MySQL 索引与事务笔记
一、索引(Index)
1、索引的核心作用
加速查询:通过减少全表扫描的 IO 消耗,直接定位目标数据
保证唯一性:如 UNIQUE 索引可约束列值不重复
优化排序:索引有序性可避免 ORDER BY 时的文件排序(Filesort)
2、索引的常见类型
类型 | 实现方式 | 适用场景 | 注意点 |
B-Tree 索引 | 基于 B + 树结构(最常用) | 范围查询(>, <, BETWEEN)、等值查询 | MySQL 默认索引类型(InnoDB/MyISAM) |
哈希索引 | 基于哈希表(键值对映射) | 等值查询(=) | 不支持范围查询,InnoDB 的HASH为自适应 |
全文索引 | 基于倒排索引(关键词映射) | 文本内容模糊搜索(如文章、评论) | MyISAM/InnoDB(5.6+)支持,需FULLTEXT |
空间索引 | 基于 R-Tree 结构 | 地理坐标、空间数据查询 | 需列类型为GEOMETRY族(如POINT) |
3、索引设计原则
选高区分度列:优先为WHERE/JOIN/ORDER BY中区分度高的列(如用户 ID、时间戳)加索引示例:SELECT * FROM user WHERE gender='男'(性别只有 2 种,区分度低,不建议加索引)
避免冗余索引:如已有(a,b)联合索引,无需单独为a加索引(前缀匹配原则)
索引列避免计算:WHERE YEAR(create_time)=2023 不会使用create_time的索引(需改为create_time >= '2023-01-01')
限制索引数量:单表索引建议不超过 5 个(写操作性能随索引数增加而下降)
4、索引失效场景
条件列使用函数或类型转换(如WHERE LEFT(name, 3)='张')
范围查询(>/<)后使用等值条件(联合索引中,范围列后的列无法使用索引)
模糊查询以通配符开头(LIKE '%张%',但LIKE '张%'可使用索引)
条件列存在NULL值(索引默认不存储NULL,IS NULL可能不生效)
二、事务(Transaction)
1、事务的 ACID 特性
原子性(Atomicity):事务中的操作要么全执行,要么全回滚(通过undo log实现)
一致性(Consistency):事务执行前后,数据库从一个一致状态转移到另一个一致状态(如转账后总金额不变)
隔离性(Isolation):多个事务并发执行时,互不干扰(通过锁、MVCC 实现)
持久性(Durability):事务提交后,数据变更永久保存(通过redo log实现)
2、事务的基本操作
-- 开启事务(隐式或显式)
BEGIN; -- 或 START TRANSACTION;
-- 业务操作(增删改查)
UPDATE account SET balance=balance-100 WHERE user_id=1;
UPDATE account SET balance=balance+100 WHERE user_id=2;
-- 提交(成功则持久化)
COMMIT;
-- 回滚(失败则撤销所有操作)
ROLLBACK;
3、事务的隔离级别
脏读:事务 A 读到事务 B 未提交的修改
不可重复读:事务 A 两次查询同一数据,结果因事务 B 的提交而不同(侧重 “修改”)
幻读:事务 A 两次查询同一范围,结果因事务 B 的插入 / 删除而不同(侧重 “增删”)
隔离级别 | 脏读 | 不可重复读 | 幻读 | 实现方式 |
READ UNCOMMITTED(读未提交) | √ | √ | √ | 无锁,直接读取未提交数据 |
READ COMMITTED(读已提交) | × | √ | √ | 行锁(语句级)+ MVCC(读已提交) |
REPEATABLE READ(可重复读) | × | × | √(部分解决) | 行锁(事务级)+ MVCC(一致性视图) |
SERIALIZABLE(串行化) | × | × | × | 表锁 + 强制事务串行执行 |
4、事务的最佳实践
短事务优先:避免长事务(可能导致锁等待、主从延迟等问题)
明确隔离级别:根据业务需求调整(如订单系统需避免脏读,用READ COMMITTED)
锁粒度控制:尽量使用行锁(InnoDB 默认),避免表锁(MyISAM 默认)
异常处理:通过TRY...CATCH或ON ERROR ROLLBACK确保事务完整性
三、索引深度解析:结构与优化
1、B + 树索引的底层特性(InnoDB 默认)
结构特点:
所有数据存储在叶子节点,非叶子节点仅存储索引键和指针,层级少(通常 3 层可支撑千万级数据)
叶子节点双向链表连接,便于范围查询(如ORDER BY或BETWEEN)
优势:
比哈希索引更适合范围查询,比二叉树更高效(减少磁盘 IO 次数)
示例:
表字段(id, name, age),为(name, age)建联合索引,B + 树按name排序,相同name按age排序,查询WHERE name='张三' AND age>20可快速定位
2、联合索引的「最左匹配原则」
核心规则:联合索引按索引列顺序匹配查询条件,必须从最左列开始,支持前缀匹配
索引(a, b, c)可匹配:a、a+b、a+b+c、a+c(不支持,因b中间缺失)
反例:WHERE b=1 无法使用该索引(未从最左列a开始)
优化技巧:将高频查询的条件列放在索引左侧(如(user_id, order_time)优于(order_time, user_id))
3、覆盖索引(Covering Index)
定义:查询所需字段全部包含在索引中,无需回表查询数据行(SELECT *无法使用覆盖索引)
-- 表:user(id, name, email)
-- 索引:(name) 无法覆盖email,需回表
-- 优化:创建索引 (name, email),查询name和email时直接通过索引返回
SELECT name, email FROM user WHERE name='李四';
作用:减少磁盘 IO,提升查询速度(尤其适合COUNT(*)、SUM()等聚合操作)
4、索引碎片与重建
碎片产生:频繁更新导致索引页分裂 / 合并,空间利用率下降。
检测:
-- 查看索引碎片率(InnoDB)
SELECT
TABLE_NAME, INDEX_NAME,
PAGE_COUNT * 16 AS INDEX_SIZE_KB, -- 每个页16KB
AVG_REC_SIZE, N_DIRECTION, N_LEAF_PAGES
FROM INFORMATION_SCHEMA.INDEX_STATISTICS
WHERE TABLE_SCHEMA = '你的数据库' AND TABLE_NAME = '表名';
重建索引:
ALTER TABLE 表名 ALTER INDEX 索引名 VISIBLE; -- 重建(MySQL 8.0+)
-- 或(传统方法)
DROP INDEX 索引名 ON 表名; CREATE INDEX 索引名 ON 表名(列);
四、事务深度解析:锁与 MVCC
1、InnoDB 的锁类型
锁类型 | 粒度 | 说明 |
行锁 | 行级别 | 分为共享锁(S锁)(允许读)和排他锁(X锁)(允许写),支持并发。 |
间隙锁(Gap Lock) | 区间锁 | 在可重复读隔离级别下,防止幻读,锁定索引间隙(如WHERE id > 10锁定 (10, +∞))。 |
表锁 | 表级别 | MyISAM 默认,InnoDB 仅在WHERE条件无索引时升级为表锁(全表扫描场景)。 |
意向锁 | 表级别 | 表示事务对行锁的意图(意向共享锁IS、意向排他锁IX),提高锁校验效率。 |
示例:
事务 A 执行SELECT * FROM user WHERE id=5 FOR UPDATE(加 X 锁),事务 B 无法修改 id=5 的行;
若 id 无索引,A 会加表级 IX 锁,B 的所有写操作阻塞
2、MVCC(多版本并发控制)
作用:在 Read Committed 和 Repeatable Read 隔离级别下,通过版本号实现无锁读,提升并发性能
核心实现:
undo log记录数据旧版本,每个事务有独立的一致性视图(版本号数组)
在 Repeatable Read 中,事务启动时生成固定版本号,后续查询始终读取该版本之前提交的数据
示例:
事务 A 启动(版本号 100),事务 B 修改数据并提交(版本号 101),A 再次查询时仍看到旧版本(避免不可重复读)
3、事务隔离级别的性能对比
隔离级别 | 并发性能 | 锁开销 | 适用场景 |
READ UNCOMMITTED | 最高 | 最低(无锁) | 临时统计、日志分析(允许脏读) |
READ COMMITTED | 高 | 行锁(语句级) | 大多数业务场景(如电商订单) |
REPEATABLE READ | 中 | 行锁 + 间隙锁 | 金融转账(需严格一致性) |
SERIALIZABLE | 最低 | 表锁 / 串行 | 极端一致性场景(几乎不用) |
4、死锁排查与解决
死锁原因:事务 A 持有锁 X 并请求锁 Y,事务 B 持有锁 Y 并请求锁 X,互相阻塞。
检测:
-- 查看最近死锁日志(需开启`innodb_print_all_deadlocks`)
SHOW ENGINE INNODB STATUS;
解决方案:
较小事务优先提交(InnoDB 自动回滚较小事务)
统一事务中操作资源的顺序(如按id升序操作,避免交叉加锁)
设置锁等待超时:SET SESSION innodb_lock_wait_timeout=5;(默认 50 秒)
五、实战优化:慢查询与事务调优
1、慢查询分析(使用EXPLAIN)
EXPLAIN SELECT * FROM order WHERE create_time > '2023-01-01';
关键字段:
type:最好为const(主键查询)、eq_ref(唯一索引),最差为ALL(全表扫描)
key:显示实际使用的索引,若为NULL则未用索引
rows:预估扫描行数,越小越好
2、事务性能优化
避免自动提交:
关闭AUTOCOMMIT(默认开启),批量操作合并为一个事务:
SET AUTOCOMMIT=0; -- 开启事务模式(需配合COMMIT/ROLLBACK)
减少锁持有时间:
将事务中的非必要操作(如计算、日志记录)移到事务外:
BEGIN;
UPDATE stock SET count=count-1 WHERE product_id=1; -- 锁操作
COMMIT;
-- 事务外执行耗时逻辑(如发送短信通知)
六、常见问题与避坑
1、索引失效的 “隐形” 场景
数据类型不一致:
WHERE user_id='123'(字段为 INT),MySQL 隐式转换为数值,可使用索引;WHERE phone=13812345678(字段为 VARCHAR 且未加引号),无法使用索引(视为数值,导致类型不匹配)
索引列允许 NULL:NULL值不会被索引存储,WHERE col IS NULL可能全表扫描(建议用默认值替代 NULL)
2、事务不回滚的常见原因
引擎不支持事务:MyISAM 引擎不支持事务,需改为 InnoDB(ALTER TABLE 表名 ENGINE=InnoDB;)
自动提交未关闭:单个UPDATE语句默认自动提交,需用BEGIN显式开启事务
DDL 操作隐式提交:事务中执行CREATE TABLE、ALTER TABLE会导致事务自动提交(DDL 不支持回滚)
七、总结
索引优化:优先为高频查询条件创建联合索引,利用覆盖索引减少回表,定期监控索引碎片
事务控制:根据业务选择隔离级别(默认REPEATABLE READ),控制事务范围,避免长事务和锁竞争
实战工具:善用EXPLAIN分析执行计划,通过SHOW ENGINE INNODB STATUS排查锁问题