0
点赞
收藏
分享

微信扫一扫

MySQL索引与事务

求阙者 05-10 12:00 阅读 7

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 的插入 / 删除而不同(侧重 “增删”)


MySQL(InnoDB)支持的隔离级别(默认:REPEATABLE READ):

隔离级别

脏读

不可重复读

幻读

实现方式

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排查锁问题

通过深入理解索引的数据结构与事务的并发控制机制,可更精准地优化 MySQL 性能,确保数据一致性与可靠性



举报

相关推荐

0 条评论