索引
- 优势:加速查询(减少磁盘 IO)、支持高效排序(减少 CPU 消耗)
- 代价:占用存储空间、降低写入(INSERT/UPDATE/DELETE)速度
需要权衡读写比例和空间成本
索引概念
有序的数据结构,高效获取数据
优缺点
| 优势 | 劣势 |
|---|---|
| 提高数据检索效率,降低数据库IO成本 | 索引列是要占用空间 |
| 通过索引对数据进行排序,降低数据排序成本,降低CPU的消耗 | 提高效率的同时降低更新表的速度 |
实现
索引是在存储引擎层实现
结构
-
二叉树
顺序插入式会形成一个链表,查询性能大大降低,检索慢
-
B-Tree(多路平衡查找树)
以一颗最大度数为5,每个节点最多存储4个key,5个指针
-
B±Tree
所有节点都会在叶子节点中出现。在原树上,增加一个只想相邻子节点的链表指针,形成带有顺序指针的B+Tree,提高区间访问的性能
InnoDB存储引擎选择使用B+Tree
- 二叉树如果顺序插入,就形成一个链表,时间复杂度为O(n),用红黑树也是二叉树。使用B+Tree层级更少,搜索效率更高
- 对于B-Tree 的每个节点都存储数据,导致单页能存的键值更少,树更高;而 B+Tree 非叶子节点只存键和指针,叶子节点存数据,层级更低,I/O 更少
- 相对与Hash索引,B+Tree支持范围匹配及排序操作:
- 非叶子节点只存键+指针,叶子节点存完整数据
- 叶子节点用链表连接 → 高效范围查询
- 层级更低 → 查询更快(通常 3~4 层可存千万级数据)
索引使用
语法
-
创建索引
SQLCREATE[UNIQUE|FULLTEXT] INDEX INDEX_NAME ON TABLE_NAME(INDEX_COL_NAME,...);**注意:**联合字段的话要在括号里写明,同时顺序也有考究
-
查看索引
SQLSHOW INDEX FROM TABLE_NAME -
删除索引
SQLDROP INDEX INDEX_NAME ON TABLE_NAME;
1.最左前缀原则
必须从最左列开始连续使用才能命中索引:
mysql
-- ✅ 命中索引
WHERE a = 1
WHERE a = 1 AND b = 2
WHERE a = 1 AND b = 2 AND c = 3
-- ❌ 不命中(跳过 a 或中间断开)
WHERE b = 2
WHERE a = 1 AND c = 3
2.回表&覆盖索引
回表:通过二级索引找到主键->再查聚簇索引拿完整数据(多一次IO)
覆盖索引:查询字段全部包含在索引中->直接返回,无需回表
主键id一般是实现回表查询
失效情况
| 场景 | 示例 | 是否失效 |
|---|---|---|
| 字符串不加引号 | WHERE name = 123(name 是 VARCHAR) |
✅ 失效 |
| 头部模糊匹配 | WHERE name LIKE '%张' |
✅ 失效 LIKE '张%' 不失效 |
| OR 条件部分无索引 | WHERE indexed_col = 1 OR no_index_col = 2 |
✅ 整个失效 |
| 对索引列使用函数/运算 | WHERE YEAR(create_time) = 2024 WHERE score + 10 > 100 |
✅ 失效 |
-
数据分布影响
如果Mysql评估使用索引比全表更慢,那么不使用索引
is null 和is not null要具体看数据分布
对索引列本身使用函数(如 UPPER(col)、DATE(col))会导致该列索引失效;SELECT 或 ORDER BY 中的计算字段不会导致 WHERE 条件的索引失效,但这些计算字段本身无法使用索引进行排序或过滤。
实践
1.最左前缀原则
联合索引(a,b,c),查询条件必须从a开始才能命中索引
联合索引查找必须按顺序来
实践
在个人项目WenJi中,AI对话的ai_chat_message表中采用了最左前缀原则:
mysql
-- ai_chat_session 表的复合索引
CREATE INDEX idx_ai_chat_session_user_status_time
ON ai_chat_session(user_id, status, last_active_time);
能够实现:
mysql
// 1. 使用全部三列 - 完全匹配
SELECT * FROM ai_chat_session
WHERE user_id = 1 AND status = 'active' AND last_active_time > '2024-01-01';
// 2. 使用前两列 - 部分匹配
SELECT * FROM ai_chat_session
WHERE user_id = 1 AND status = 'active';
// 3. 只使用第一列 - 部分匹配
SELECT * FROM ai_chat_session
WHERE user_id = 1;
2.回表与覆盖查询
通过二级索引找到主键后,在回到聚集索引中获取完整数据行的过程
实践
在ai_chat_message表中通过使用session_id索引,实现信息的查找
mysql
-- ai_chat_message 表有索引 idx_session_id(session_id)
-- 这个查询会发生回表
SELECT * FROM ai_chat_message WHERE session_id = 'abc123';
执行流程
- 在 idx_session_id 二级索引中找到 session_id = 'abc123' 的记录
- 获取该记录的主键 message_id
- 拿着 message_id 去聚簇索引(主键索引)中查找完整行数据
- 返回所有字段 (*)
3.覆盖索引
索引中包含了查询所需的所有字段,数据库可以直接从索引获取全部数据,无需再访问表的数据行
这样避免了回表,减少I/O操作,效率提高
实践
在项目中我把返回所有值改成只返回需要的字段值
mysql
-- ❌ 回表查询(需要获取所有字段)
SELECT * FROM ai_chat_message WHERE session_id = 'abc123';
-- ✅ 覆盖索引(只查询索引中包含的字段)
SELECT message_id, session_id, role, content, create_time
FROM ai_chat_message
WHERE session_id = 'abc123';
4.索引失效
对索引列进行函数运算或表达式计算
在项目中为了去实现用户进入景区游玩能相应获得积分的功能,我在sql语句中判断景点是否在规定的范围内的函数运算
sql
-- HeritageSiteMapper.xml 中的距离计算
SELECT *, (6371 * acos(...)) AS distance
FROM heritage_sites
WHERE status = 1
ORDER BY distance ASC;