一、联合索引和最左匹配原则
1、最左匹配原则
在建立联合索引后,查询条件需要按照索引的顺序从左到右完全匹配,才能使用到索引。
2、相关问题
前提:user表包含主键ID,A,B,C列,创建(A,B,C)联合索引
(1)select * from user where B=2 and A=3 and C=4; 会用到联合索引吗?
会。优化器会将查询条件自动重排序为 where A=3 and B=2 and C=4;
- 可用索引的查询条件:where A=?; where A=? and B=?; where A=? and B=? and C=?;
- 不可用索引的查询条件:where B=?;(跳过了A)。 where A=? and C=?;(跳过了B)。WHERE A = 3 AND B = '2' AND C = 4;(A列若是字符串类型,发生隐式转换导致索引失效)。
(2)select * from user where B = 2 and A > 3 and C = 4; 会用到联合索引吗?
部分使用。自动重排,先使用 A 做范围查询,在使用B筛选(索引下推),C无法使用索引
联合索引的使用遵循 "最左前缀原则" ,并且 一旦遇到范围查询(如 >, <, BETWEEN, LIKE 'xx%' 等),后续列将无法用于索引查找
(3)select * from user where A = 2 and B > 3 and C = 4; 会用到联合索引吗?
会。A等值匹配精准定位,B在A=2的范围内进行范围扫描,C会使用索引下推。
原则:创建联合索引时,尽量把用于等值查找的列放在最左边。
二、深分页
SELECT * FROM orders ORDER BY id LIMIT 1000000, 20;
上面的SQL就是深分页,执行 LIMIT offset, size 时,必须先扫描并跳过前 offset 行。offset 越大,扫描到的无用数据越多,I/O 和 CPU 开销越大。
解决方案:
1、在自增主键或时间字段排序的场景下,可以记录上一页最后一条数据的id,下一页在这个id之后开始查询。可以使用到id索引,但是不能随意跳转页。
-- 第一页
SELECT * FROM orders ORDER BY id LIMIT 20;
-- 假设最后一条 id = 99999999
-- 第二页
SELECT * FROM orders WHERE id > 99999999 ORDER BY id LIMIT 20;
-- 第 N 页:始终基于上一页的边界值
2、延迟关联,必须用offset时,减少回表次数。但仍需扫描 100 万索引项,只是比全表回表快一些,无法根本解决深分页问题。
-- 先通过覆盖索引找到目标 id(避免回表)
SELECT o.*
FROM orders o
INNER JOIN (
SELECT id
FROM orders
ORDER BY id
LIMIT 1000000, 20
) tmp ON o.id = tmp.id;
3、业务层限制,只给用户展示前100页的信息,其他信息使用搜索查询。
三、undo log 和 redo log
1、undo log(回滚日志 )
记录事务开始前的数据状态,是 InnoDB 存储引擎 实现 事务原子性(Atomicity) 和 多版本并发控制(MVCC) 的核心机制之一。
实现事务的原子性(回滚): undo log 本身存储在 InnoDB 的 undo tablespace (MySQL 8.0+)或 系统表空间 (旧版本)中,每个事务修改数据前,会先在 undo log 中记录反向操作,从而支持事务的回滚
INSERT→ 记录DELETEUPDATE→ 记录修改前的旧值DELETE→ 记录整行数据
对于MVCC的支持体现在 :当一个事务正在读取某行数据时,另一个事务可能正在修改它。InnoDB 通过 undo log 构建该行的历史版本 ,让读事务看到符合其一致性视图的数据(避免加锁,提高并发),例如:
- 事务 A 在时间 T1 开始,执行
SELECT * FROM account WHERE id = 1; - 事务 B 在 T2 修改了该行并提交
- 事务 A 再次查询时,仍应看到 T1 时刻的值 → InnoDB 从 undo log 中找到对应版本返回
事务提交后,undo log 不会立即删除。InnoDB 后台有 purge 线程,定期清理不再被任何事务需要的 undo log。判断依据是,所有活跃事务的 Read View 都不需要该 undo 版本
2、redo log(重做日志)
用于保证事务的持久性、支持崩溃恢复。记录事务提交标志,InnoDB通过检查redo log中是否有事务的提交标志,来判断该事务是否已经提交。redo log记录事务的ID、操作、状态。
两阶段提交记录:事务的提交过程如下:
- redo log写入事务信息,状态是PREPARE
- bin log写入事务信息
- redo log写入事务信息,状态是COMMIT
bin log只记录一次事务信息,在崩溃恢复的过程中,特定情况下起到"仲裁"作用。InnoDB是基于 redo log 做崩溃恢复的。
崩溃恢复流程:
- InnoDB 扫描 redo log,对于有commit记录的事务,直接重做
- 如果事务只有prepare状态的数据,则去查询bin log,bin log中有事务信息则说明事务已提交,会重做;bin log中有事务信息则说明事务未提交,会回滚
四、索引跳跃扫描
MySQL 8.0 引入的一种优化器策略 ,默认是开启的,用于在 复合索引(联合索引)的前导列(leading column)未出现在查询条件中时,仍然能高效利用该索引。
CREATE TABLE t (
gender ENUM('M', 'F') NOT NULL, -- 只有 2 个值
age INT NOT NULL,
name VARCHAR(50),
KEY idx_gender_age (gender, age) -- 联合索引
);
-- 注意:WHERE 中没有 gender!
SELECT * FROM t WHERE age = 25;
gender 取值很少(只有 'M' 和 'F'),可以执行下列查询:
SELECT * FROM t WHERE gender = 'M' AND age = 25
UNION ALL
SELECT * FROM t WHERE gender = 'F' AND age = 25
虽然执行了 2 次索引查找,但总成本 远低于全表扫描。这就是"跳跃":跳过前导列的限制,通过枚举其离散值来复用索引。
触发条件:前导列是 低基数字段(如性别、状态、布尔值、枚举),如果前导列的值有 100 万个不同值,枚举成本太高,不会触发。
EXPLAIN的EXTRA列中显示Using index for skip scan,就说明使用了索引跳跃。