6.Mysql锁机制与优化实践以及MVCC底层原理剖析

发布时间:2026/6/29 16:32:46
6.Mysql锁机制与优化实践以及MVCC底层原理剖析 Mysql锁机制与优化实践以及MVCC底层原理剖析 知识体系总览一、锁机制基础✅1. 锁的概念与分类✅2. 读锁与写锁读锁共享锁S锁 Shared写锁排它锁X锁 eXclusive✅3. 意向锁Intention Lock 小结锁分类对比表二、表锁✅1. 表锁特性与基本操作三、页锁✅1. 页锁特性四、行锁✅1. 行锁特性✅2. 行锁升级为表锁的问题✅3. RR级别行锁升级为表锁的原因分析 表锁 vs 行锁对比五、间隙锁与临键锁✅1. 间隙锁Gap Lock✅2. 临键锁Next-Key Locks 三种行级锁对比六、锁等待与死锁分析✅1. 锁等待状态分析✅2. INFORMATION_SCHEMA锁相关数据表✅3. 死锁问题分析 锁问题排查命令速查表七、锁优化实践八、MVCC多版本并发控制机制✅1. MVCC概念与原理✅2. undo日志版本链✅3. read view机制详解✅4. 版本链比对规则✅5. 删除数据的特殊处理✅6. RR与RC隔离级别的MVCC实现差异 RR vs RC隔离级别MVCC实现对比 MVCC总结 全文总结✅1. 锁分类体系✅2. 行锁升级问题✅3. 间隙锁与幻读✅4. 锁优化核心原则✅5. MVCC实现原理✅6. 死锁排查✅7. 关键SQL速记 知识体系总览Mysql锁机制与MVCC底层原理 ├── 一、锁机制基础 │ ├── ✅1. 锁的概念与分类 │ ├── ✅2. 读锁共享锁与写锁排他锁 │ ├── ✅3. 意向锁IS/IX │ └── 小结锁分类对比表 ├── 二、表锁 │ └── ✅1. 表锁特性与基本操作 ├── 三、页锁 │ └── ✅1. 页锁特性 ├── 四、行锁 │ ├── ✅1. 行锁特性 │ ├── ✅2. 行锁升级为表锁的问题 │ └── ✅3. RR级别行锁升级原因分析 ├── 五、间隙锁与临键锁 │ ├── ✅1. 间隙锁Gap Lock │ └── ✅2. 临键锁Next-Key Locks ├── 六、锁等待与死锁分析 │ ├── ✅1. 锁等待状态分析 │ ├── ✅2. INFORMATION_SCHEMA锁相关表 │ └── ✅3. 死锁问题分析 ├── 七、锁优化实践 ├── 八、MVCC多版本并发控制机制 │ ├── ✅1. MVCC概念与原理 │ ├── ✅2. undo日志版本链 │ ├── ✅3. read view机制详解 │ ├── ✅4. 版本链比对规则 │ ├── ✅5. 删除数据的特殊处理 │ └── ✅6. RR与RC隔离级别的MVCC实现 └── 全文总结一、锁机制基础✅1. 锁的概念与分类锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中除了传统的计算资源如CPU、RAM、I/O等的争用以外数据也是一种供需要用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题锁冲突也是影响数据库并发访问性能的一个重要因素。 核心锁是数据库保证并发访问一致性的核心机制锁冲突直接影响数据库并发性能。锁从不同维度的分类从性能上分乐观锁(用版本对比或CAS机制)和悲观锁乐观锁适合读操作较多的场景悲观锁适合写操作较多的场景如果在写操作较多的场景使用乐观锁会导致比对次数过多影响性能从对数据操作的粒度分表锁、页锁、行锁从对数据库操作的类型分读锁和写锁(都属于悲观锁)还有意向锁✅2. 读锁与写锁读锁共享锁S锁 Shared针对同一份数据多个读操作可以同时进行而不会互相影响比如select*fromTwhereid1lockinsharemode写锁排它锁X锁 eXclusive当前写操作没有完成前它会阻断其他写锁和读锁数据修改操作都会加写锁查询也可以通过for update加写锁比如select*fromTwhereid1forupdate 关键理解读锁会阻塞写但是不会阻塞读。而写锁则会把读和写都阻塞。✅3. 意向锁Intention Lock又称I锁针对表锁主要是为了提高加表锁的效率是mysql数据库自己加的。当有事务给表的数据行加了共享锁或排他锁同时会给表设置一个标识代表已经有行锁了其他事务要想对表加表锁时就不必逐行判断有没有行锁可能跟表锁冲突了直接读这个标识就可以确定自己该不该加表锁。特别是表中的记录很多时逐行判断加表锁的方式效率很低。而这个标识就是意向锁。意向锁主要分为意向共享锁IS锁对整个表加共享锁之前需要先获取到意向共享锁意向排他锁IX锁对整个表加排他锁之前需要先获取到意向排他锁 面试要点意向锁是MySQL内部自动维护的用户无法手动干预。它的存在就是为了让表锁的加锁判断从O(n)降到O(1)。行锁分两种表锁也分两种组合起来一共四种情况结果完全不同表里持有的行锁对应意向锁你想加表读锁LOCK TABLES t READ你想加表写锁LOCK TABLES t WRITE行读锁S如LOCK IN SHARE MODEIS✅能加❌ 冲突等待行写锁X如FOR UPDATE/UPDATEIX❌ 冲突等待❌ 冲突等待一句话记忆只有表里是行读锁 你想加表读锁这一种组合不冲突其余三种都会冲突等待 小结锁分类对比表维度类型说明性能乐观锁版本对比/CAS适合读多写少性能悲观锁先加锁再操作适合写多读少操作粒度表锁锁整张表开销小并发度低操作粒度页锁锁一个页介于表锁和行锁之间操作粒度行锁锁一行开销大并发度高操作类型读锁(S锁)共享不阻塞读阻塞写操作类型写锁(X锁)排他阻塞读和写操作类型意向锁(I锁)表级标识提高加表锁效率二、表锁✅1. 表锁特性与基本操作每次操作锁住整张表。开销小加锁快不会出现死锁锁定粒度大发生锁冲突的概率最高并发度最低一般用在整表数据迁移的场景。-- 建表SQLCREATETABLEmylock(idINT(11)NOTNULLAUTO_INCREMENT,NAMEVARCHAR(20)DEFAULTNULL,PRIMARYKEY(id))ENGINEMyISAMDEFAULTCHARSETutf8;-- 插入数据INSERTINTOtest.mylock(id,NAME)VALUES(1,a);INSERTINTOtest.mylock(id,NAME)VALUES(2,b);INSERTINTOtest.mylock(id,NAME)VALUES(3,c);INSERTINTOtest.mylock(id,NAME)VALUES(4,d);-- 手动增加表锁locktable表名称read(write),表名称2read(write);-- 查看表上加过的锁showopentables;-- 删除表锁unlocktables; 关键理解MyISAM在执行查询语句SELECT前会自动给涉及的所有表加读锁在执行update、insert、delete操作会自动给涉及的表加写锁。三、页锁✅1. 页锁特性只有BDB存储引擎支持页锁页锁就是在页的粒度上进行锁定锁定的数据资源比行锁要多因为一个页中可以有多个行记录。当我们使用页锁的时候会出现数据浪费的现象但这样的浪费最多也就是一个页上的数据行。页锁的开销介于表锁和行锁之间会出现死锁。锁定粒度介于表锁和行锁之间并发度一般。四、行锁✅1. 行锁特性每次操作锁住一行数据。**开销大加锁慢**会出现死锁锁定粒度最小发生锁冲突的概率最低并发度最高。InnoDB相对于MYISAM的最大不同有两点InnoDB支持事务TRANSACTIONInnoDB支持行级锁 注意InnoDB的行锁实际上是针对索引加的锁(在索引对应的索引项上做标记)不是针对整个行记录加的锁。并且该索引不能失效否则会从行锁升级为表锁。(RR级别会升级为表锁RC级别不会升级为表锁)✅2. 行锁升级为表锁的问题比如我们在RR级别执行如下sqlselect*fromaccountwherenamelileiforupdate;--where条件里的name字段无索引则其它Session对该表任意一行记录做修改操作都会被阻塞住。✅3. RR级别行锁升级为表锁的原因分析因为在RR隔离级别下需要解决不可重复读和幻读问题所以在遍历扫描聚集索引记录时为了防止扫描过的索引被其它事务修改(不可重复读问题) 或 间隙被其它事务插入记录(幻读问题)从而导致数据不一致所以MySQL的解决方案就是把所有扫描过的索引记录和间隙都锁上这里要注意并不是直接将整张表加表锁因为不一定能加上表锁可能会有其它事务锁住了表里的其它行记录。 面试要点InnoDB在RR级别下无索引的查询条件会导致行锁升级本质是MySQL对扫描到的所有记录加锁来保证隔离性实际效果类似表锁但机制上不同于表锁。 表锁 vs 行锁对比特性表锁行锁开销小大加锁速度快慢死锁不会会粒度大小锁冲突概率高低并发度低高适用场景整表迁移高并发OLTP存储引擎MyISAM/MemoryInnoDB五、间隙锁与临键锁✅1. 间隙锁Gap Lock间隙锁锁的就是两个值之间的空隙间隙锁是在可重复读隔离级别下才会生效。Mysql默认级别是repeatable-read有幻读问题间隙锁是可以解决幻读问题的。假设account表里数据如下那么间隙就有 id 为 (3,10)(10,20)(20,正无穷) 这三个区间。在Session_1下面执行如下sqlselect*fromaccountwhereid18forupdate;则其他Session没法在这个(10,20)这个间隙范围里插入任何数据。如果执行下面这条sqlselect*fromaccountwhereid25forupdate;则其他Session没法在这个(20,正无穷)这个间隙范围里插入任何数据。也就是说只要在间隙范围内锁了一条不存在的记录会锁住整个间隙范围不锁边界记录这样就能防止其它Session在这个间隙范围内插入数据就解决了可重复读隔离级别的幻读问题。 核心间隙锁只在RR级别生效只锁间隙不锁边界记录专门用于解决幻读问题。✅2. 临键锁Next-Key LocksNext-Key Locks是行锁与间隙锁的组合。相当于把当前记录锁住行锁同时锁住当前记录前面的间隙间隙锁形成(left_gap, record]的左开右闭区间。 面试要点InnoDB在RR级别下的默认加锁算法就是Next-Key Lock它可以同时解决不可重复读通过行锁和幻读通过间隙锁问题。 三种行级锁对比锁类型锁范围作用生效级别Record Lock单行记录防止其他事务修改/删除RC/RRGap Lock间隙不锁边界防止其他事务在间隙插入仅RRNext-Key Lock记录前间隙防止修改/删除插入仅RR六、锁等待与死锁分析✅1. 锁等待状态分析通过检查InnoDB_row_lock状态变量来分析系统上的行锁的争夺情况showstatuslikeinnodb_row_lock%;对各个状态量的说明如下Innodb_row_lock_current_waits: 当前正在等待锁定的数量Innodb_row_lock_time: 从系统启动到现在锁定总时间长度Innodb_row_lock_time_avg: 每次等待所花平均时间Innodb_row_lock_time_max从系统启动到现在等待最长的一次所花时间Innodb_row_lock_waits: 系统启动后到现在总共等待的次数对于这5个状态变量比较重要的主要是Innodb_row_lock_time_avg等待平均时长Innodb_row_lock_waits等待总次数Innodb_row_lock_time等待总时长 关键理解当等待次数很高而且每次等待时长也不小的时候我们需要分析系统中为什么会有如此多的等待然后根据分析结果着手制定优化计划。✅2. INFORMATION_SCHEMA锁相关数据表-- 查看事务select*fromINFORMATION_SCHEMA.INNODB_TRX;-- 查看锁8.0之后需要换成这张表performance_schema.data_locksselect*fromINFORMATION_SCHEMA.INNODB_LOCKS;-- 查看锁等待8.0之后需要换成这张表performance_schema.data_lock_waitsselect*fromINFORMATION_SCHEMA.INNODB_LOCK_WAITS;-- 释放锁trx_mysql_thread_id可以从INNODB_TRX表里查看到killtrx_mysql_thread_id-- 查看锁等待详细信息showengineinnodbstatus;✅3. 死锁问题分析settx_isolationrepeatable-read;Session_1执行select * from account where id1 for update;Session_2执行select * from account where id2 for update;Session_1执行select * from account where id2 for update;Session_2执行select * from account where id1 for update;查看近期死锁日志信息show engine innodb status;大多数情况mysql可以自动检测死锁并回滚产生死锁的那个事务但是有些情况mysql没法自动检测死锁这种情况我们可以通过日志分析找到对应事务线程id可以通过kill杀掉。 锁问题排查命令速查表命令用途版本注意show status like innodb_row_lock%行锁等待统计通用SELECT * FROM INNODB_TRX查看当前事务通用SELECT * FROM INNODB_LOCKS查看锁信息8.0用performance_schema.data_locks代替SELECT * FROM INNODB_LOCK_WAITS查看锁等待8.0用performance_schema.data_lock_waits代替kill thread_id释放指定事务的锁通用show engine innodb status查看锁等待/死锁详情通用七、锁优化实践尽可能让所有数据检索都通过索引来完成避免无索引行锁升级为表锁合理设计索引尽量缩小锁的范围尽可能减少检索条件范围避免间隙锁尽量控制事务大小减少锁定资源量和时间长度涉及事务加锁的sql尽量放在事务最后执行尽可能用低的事务隔离级别 面试要点锁优化的核心思路——通过索引减少锁范围、通过事务拆小减少锁时长、通过降低隔离级别减少锁种类。八、MVCC多版本并发控制机制✅1. MVCC概念与原理Mysql在可重复读隔离级别下如何保证事务较高的隔离性同样的sql查询语句在一个事务里多次执行查询结果相同就算其它事务对数据有修改也不会影响当前事务sql语句的查询结果。这个隔离性就是靠MVCC(Multi-Version Concurrency Control)机制来保证的对一行数据的读和写两个操作默认是不会通过加锁互斥来保证隔离性避免了频繁加锁互斥而在串行化隔离级别为了保证较高的隔离性是通过将所有操作加锁互斥来实现的。Mysql在读已提交和可重复读隔离级别下都实现了MVCC机制。 核心MVCC实现了读写不冲突的并发控制读操作不加锁快照读写操作加锁当前读两者互不阻塞。这是InnoDB高并发的核心秘密。✅2. undo日志版本链undo日志版本链是指一行数据被多个事务依次修改过后在每个事务修改完后Mysql会保留修改前的数据undo回滚日志并且用两个隐藏字段trx_id和roll_pointer把这些undo日志串联起来形成一个历史记录版本链 关键理解每行数据有隐藏字段trx_id最后修改该行的事务ID和roll_pointer指向上一个版本的undo日志指针通过这两个字段将所有历史版本串联成一条链。✅3. read view机制详解在可重复读隔离级别当事务开启执行任何查询sql时会生成当前事务的一致性视图read-view该视图在事务结束之前永远都不会变化(如果是读已提交隔离级别在每次执行查询sql时都会重新生成read-view)这个视图由执行查询时所有未提交事务id数组数组里最小的id为min_id和已创建的最大事务idmax_id组成事务里的任何sql查询结果需要从对应版本链里的最新数据开始逐条跟read-view做比对从而得到最终的快照结果。✅4. 版本链比对规则如果 row 的 trx_id 落在绿色部分(trx_idmin_id)表示这个版本是已提交的事务生成的这个数据是可见的如果 row 的 trx_id 落在红色部分(trx_idmax_id)表示这个版本是由将来启动的事务生成的是不可见的(若 row 的 trx_id 就是当前自己的事务是可见的如果 row 的 trx_id 落在黄色部分(min_id trx_id max_id)那就包括两种情况a. 若 row 的 trx_id 在视图数组中表示这个版本是由还没提交的事务生成的不可见(若 row 的 trx_id 就是当前自己的事务是可见的)b. 若 row 的 trx_id 不在视图数组中表示这个版本是已经提交了的事务生成的可见。✅5. 删除数据的特殊处理对于删除的情况可以认为是update的特殊情况会将版本链上最新的数据复制一份然后将trx_id修改成删除操作的trx_id同时在该条记录的头信息record header里的deleted_flag标记位写上true来表示当前记录已经被删除在查询时按照上面的规则查到对应的记录如果delete_flag标记位为true意味着记录已被删除则不返回数据。✅6. RR与RC隔离级别的MVCC实现差异readview和可见性算法其实就是记录了sql查询那个时刻数据库里提交和未提交所有事务的状态。要实现RR隔离级别事务里每次执行查询操作readview都是使用第一次查询时生成的readview也就是都是以第一次查询时当时数据库里所有事务提交状态来比对数据是否可见当然可以实现每次查询的可重复读的效果了。要实现RC隔离级别事务里每次执行查询操作readview都会按照数据库当前状态重新生成readview也就是每次查询都是跟数据库里当前所有事务提交状态来比对数据是否可见当然实现的就是每次都能查到已提交的最新数据效果了。 重要警告begin/start transaction命令并不是一个事务的起点在执行到它们之后的第一个修改操作或加排它锁操作(比如select…for update)的语句事务才真正启动才会向mysql申请真正的事务idmysql内部是严格按照事务的启动顺序来分配事务id的。 RR vs RC隔离级别MVCC实现对比特性RR可重复读RC读已提交read-view生成时机事务第一次查询时生成每次查询重新生成read-view是否变化事务期内不变每次查询都变可见性基准第一次查询时的快照每次查询时的最新状态效果可重复读每次读到最新已提交数据解决幻读✅通过MVCC 间隙锁❌ MVCC总结MVCC机制的实现就是通过read-view机制与undo版本链比对机制使得不同的事务会根据数据版本链对比规则读取同一条数据在版本链上的不同版本数据。 全文总结✅1. 锁分类体系从性能乐观/悲观、粒度表/页/行、类型读锁/写锁/意向锁三个维度全面理解MySQL锁体系。InnoDB的行锁基于索引实现。✅2. 行锁升级问题RR级别下无索引的查询条件会导致行锁升级为锁住所有扫描到的记录效果类似表锁。解决确保查询走索引。✅3. 间隙锁与幻读间隙锁只在RR级别生效通过锁住间隙防止插入来解-决幻读问题。Next-Key Lock是行锁间隙锁的组合。✅4. 锁优化核心原则减少锁范围走索引→减少锁时长缩短事务→减少锁种类降低隔离级别。✅5. MVCC实现原理undo日志版本链 read-view可见性比对 MVCC。RR级别下read-view在事务内不变RC级别下每次查询重新生成。✅6. 死锁排查show engine innodb status查看死锁信息多数情况下MySQL自动检测并回滚特殊情况需手动kill。✅7. 关键SQL速记-- 共享锁读锁SELECT...LOCKINSHAREMODE;-- 排他锁写锁SELECT...FORUPDATE;-- 行锁状态查看SHOWSTATUSLIKEinnodb_row_lock%;-- 死锁与锁等待信息SHOWENGINEINNODBSTATUS;