Mysql学习(二)-- 事务和锁

📅 2026/8/26 13:31:37
Mysql学习(二)-- 事务和锁
1. 事务1.1 什么是数据库事务事务是一个不可分割的数据库操作序列也是数据库并发控制的基本单位其执行的结果必须使数据库从一种一致性状态变到另一种一致性状态。事务是逻辑上的一组操作要么都执行要么都不执行。事务最经典也经常被拿出来说例子就是转账了。假如小明要给小红转账1000元这个转账会涉及到两个关键操作就是将小明的余额减少1000元将小红的余额增加1000元。万一在这两个操作之间突然出现错误比如银行系统崩溃导致小明余额减少而小红的余额没有增加这样就不对了。事务就是保证这两个关键操作要么都成功要么都要失败。1.2 事物的四大特性(ACID)关系性数据库需要遵循ACID规则具体内容如下原子性 事务是最小的执行单位不允许分割。事务的原子性确保动作要么全部完成要么完全不起作用一致性 执行事务前后数据保持一致多个事务对同一个数据读取的结果是相同的隔离性 并发访问数据库时一个用户的事务不被其他事务所干扰各并发事务之间数据库是独立的持久性 一个事务被提交之后。它对数据库中数据的改变是持久的即使数据库发生故障也不应该对其有任何影响。1.3 脏读幻读不可重复读脏读一个事务读取到另一个事务未提交的数据例比如银行取钱事务A开启事务此时切换到事务B事务B开启事务–取走100元此时切换回事务A事务A读取的肯定是数据库里面的原始数据因为事务B取走了100块钱并没有提交数据库里面的账务余额肯定还是原始余额这就是脏读。不可重复读一个事务的操作导致另一个事务前后两次读取到不同的数据一个事务读取另一个事务已提交的数据例以银行取钱为例事务A开启事务–查出银行卡余额为1000元此时切换到事务B事务B开启事务–事务B取走100元–提交数据库里面余额变为900元此时切换回事务A事务A再查一次查出账户余额为900元这样对事务A而言在同一个事务内两次读取账户余额数据不一致这就是不可重复读。虚读幻读一个事务的操作导致另一个事务前后两次查询的结果的数据量不同。一个事务读取到另一个事务已提交后添加的数据例事物A查询数据库中有没有id为1的数据查到没有此时事物B进来直接往数据库中插入了一条id为1的数据此时事物B再插就会出错更新丢失2个并发事务同时对一个结果修改后提交的事务忽略了前一个事务对数据库的影响造成了先提交的事务对数据库的影响丢失例比如学生信息事务A开启事务–修改所有学生当天签到状况为false此时切换到事务B事务B开启事务–事务B插入了一条学生数据此时切换回事务A事务A提交的时候发现了一条自己没有修改过的数据这就是幻读就好像发生了幻觉一样。幻读出现的前提是并发的事务中有事务发生了插入、删除操作。1.3.1 不可重复度和幻读的区别不可重复读 针对的是一份数据的修改幻读 针对的是行数修改1.4 事务的隔离级别为了达到事务的四大特性数据库定义了4种不同的事务隔离级别由低到高依次为Read uncommitted、Read committed、Repeatable read、Serializable这四个级别可以逐个解决脏读、不可重复读、幻读这几类问题。隔离级别脏读不可重复读幻读加锁读READ-UNCOMMITTED√√√×READ-COMMITTED×√√×REPEATABLE-READ××√×SERIALIZABLE×××√READ-UNCOMMITTED(读取未提交) 最低的隔离级别允许读取尚未提交的数据变更可能会导致脏读、幻读或不可重复读。READ-COMMITTED(读取已提交) 允许读取并发事务已经提交的数据可以阻止脏读但是幻读或不可重复读仍有可能发生。REPEATABLE-READ(可重复读) 对同一字段的多次读取结果都是一致的除非数据是被本身事务自己所修改可以阻止脏读和不可重复读但幻读仍有可能发生。SERIALIZABLE(可串行化) 最高的隔离级别完全服从ACID的隔离级别。所有的事务依次逐个执行这样事务之间就完全不可能产生干扰也就是说该级别可以防止脏读、不可重复读以及幻读。这里需要注意的是Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别事务隔离机制的实现基于锁机制和并发调度。其中并发调度使用的是MVCC多版本并发控制通过保存修改的旧版本信息来支持并发一致性读和回滚等特性。因为隔离级别越低事务请求的锁越少所以大部分数据库系统的隔离级别都是READ-COMMITTED(读取提交内容):但是你要知道的是InnoDB 存储引擎默认使用 REPEATABLE-READ可重读并不会有任何性能损失。InnoDB 存储引擎在 分布式事务 的情况下一般会用到SERIALIZABLE(可串行化)隔离级别。2. 锁2.1 数据库并发场景在高并发场景下不考虑其他中间件的情况下数据库会存在以下场景读读不存在任何问题也不需要并发控制。读写有线程安全问题可能会造成事务隔离性问题可能遇到脏读幻读不可重复读。写写有线程安全问题可能会存在更新丢失问题比如第一类更新丢失第二类更新丢失。针对以上问题SQL 标准规定不同隔离级别下可能发生的问题不一样MySQL 四大隔离级别及存在的问题隔离级别脏读不可重复读幻读加锁读READ-UNCOMMITTED√√√×READ-COMMITTED×√√×REPEATABLE-READ××√×SERIALIZABLE×××√可以看到MySQL 在 REPEATABLE READ 隔离级别实际上就解决了不可重复度问题基本解决了幻读问题但在极端情况下仍然存在幻读现象。那么有什么方式来解决呢一般来说有两种方案2.1.1 读操作 MVCC 写操作加锁对于读在 RR 级别的 MVCC 下当一个事务开启的时候会产生一个 ReadView然后通过 ReadView 找到符合条件的历史版本而这个版本则是由 undo 日志构建的而在生成 ReadView 的时候其实就是生成了一个快照所以此时的 SELECT 查询也就是快照读或者一致性读我们知道在 RR 下一个事务在执行过程中只有第一次执行 SELECT 操作才会生成一个 ReadView之后的 SELECT 操作都复用这个 ReadView这样就避免了不可重复读和很大程度上避免了幻读的问题。对于写由于在快照读或一致性读的过程中并不会对表中的任何记录做加锁操作并且 ReadView 的事务是历史版本而对于写操作的最新版本两者并不会冲突所以其他事务可以自由的对表中的记录做改动。2.1.2 读写操作都加锁如果我们的一些业务场景不允许读取记录的旧版本而是每次都必须去读取记录的最新版本比方在银行存款的事务中你需要先把账户的余额读出来然后将其加上本次存款的数额最后再写到数据库中。在将账户余额读取出来后就不想让别的事务再访问该余额直到本次存款事务执行完成其他事务才可以访问账户的余额。这样在读取记录的时候也就需要对其进行加锁操作这样也就意味着读操作和写操作也像写-写操作那样排队执行。对于脏读是因为当前事务读取了另一个未提交事务写的一条记录但如果另一个事务在写记录的时候就给这条记录加锁那么当前事务就无法继续读取该记录了所以也就不会有脏读问题的产生了。对于不可重复读是因为当前事务先读取一条记录另外一个事务对该记录做了改动之后并提交之后当前事务再次读取时会获得不同的值如果在当前事务读取记录时就给该记录加锁那么另一个事务就无法修改该记录自然也不会发生不可重复读了。对于幻读是因为当前事务读取了一个范围的记录然后另外的事务向该范围内插入了新记录当前事务再次读取该范围的记录时发现了新插入的新记录我们把新插入的那些记录称之为幻影记录。怎么理解这个范围如下假如表 user 中只有一条id1的数据。当事务 A 执行一个id 1的查询操作能查询出来数据如果是一个范围查询如 id in(1,2)必然只会查询出来一条数据。此时事务 B 执行一个id 2的新增操作并且提交。此时事务 A 再次执行id in(1,2)的查询就会读取出 2 条记录因此产生了幻读。注由于 RR 可重复读的原因其实是查不出 id 2的记录的所以如果执行一次 update … where id 2再去范围查询就能查出来了。采用加锁的方式解决幻读问题就有不太容易了因为当前事务在第一次读取记录时那些幻影记录并不存在所以读取的时候加锁就有点麻烦因为并不知道给谁加锁。那么 InnoDB 是如何解决的呢我们先来看看 InnoDB 存储引擎有哪些锁。2.2 锁的分类在 MySQL 官方文档 中InnoDB 存储引擎介绍了以下几种锁同样看起来仍然一头雾水但我们可以按照学习 JDK 中锁的方式来进行分类2.3 锁分类按粒度分在关系型数据库中可以按照锁的粒度把数据库锁分为行级锁(INNODB引擎)、表级锁(MYISAM引擎)和页级锁(BDB引擎 )。MyISAM采用表级锁(table-level locking)。InnoDB支持行级锁(row-level locking)和表级锁默认为行级锁2.3.1 行级锁行级锁是Mysql中锁定粒度最细的一种锁表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小但加锁的开销也最大。行级锁分为共享锁 和 排他锁。特点开销大加锁慢会出现死锁锁定粒度最小发生锁冲突的概率最低并发度也最高。实现方式查询操作锁定查询时共享锁lock in share modeselect math from zje where math60 lock in share mode排他锁for updateselect math from zje where math 60 for updateDML操作当执行 UPDATE、DELETE、INSERT注意行锁必须有索引才能实现否则会自动锁全表那么就不是行锁了此处的索引即可以是主键索引也可以是普通索引。主键索引在主键索引上加锁普通索引在普通索引上加锁然后去普通索引关联的主键索引的数据行上加锁两个事务不能锁同一个索引一个问题走普通索引可能带来的“锁放大”风险正是因为普通索引需要“回表”去锁主键这里极易引发严重的锁阻塞问题。举个经典的例子假设有一张表name 字段有普通索引id 是主键。事务 A 执行UPDATE user SET age 20 WHERE name 张三;事务 B 执行UPDATE user SET age 21 WHERE id 100;如果 name ‘张三’ 对应的 id 刚好是 100会发生什么事务 A 虽然只修改了 name‘张三’ 的数据但它不仅锁了 name 索引上的记录还锁住了 id100 的主键记录。事务 B 试图修改 id100 的记录发现主键已经被事务 A 锁住了于是事务 B 被阻塞。2.3.2 表级锁表级锁是MySQL中锁定粒度最大的一种锁表示对当前操作的整张表加锁它实现简单资源消耗较少被大部分MySQL引擎支持。最常使用的MYISAM与INNODB都支持表级锁定。表级锁定分为表共享读锁共享锁与表独占写锁排他锁。实现方式MyISAM 引擎的 DML 操作MyISAM 引擎不支持行锁。当对 MyISAM 表执行 UPDATE、DELETE、INSERT 时会自动加表级写锁执行 SELECT 时会加表级读锁。InnoDB 行锁升级为表锁核心避坑点当 InnoDB 执行 DML 语句时如果** WHERE 条件未命中有效索引**例如字段没建索引、发生了隐式类型转换、对索引列使用了函数等导致优化器选择全表扫描InnoDB 会对扫描到的所有行加锁实际效果等同于锁住了整张表。执行数据定义语句DDL当执行 ALTER TABLE、DROP TABLE、CREATE INDEX 等修改表结构的 DDL 语句时MySQL 会自动触发元数据锁MDL 写锁这会阻塞该表上的所有读写操作本质上起到了表锁的作用。显式手动加表锁开发者通过 LOCK TABLES … READ/WRITE 语句主动请求锁定整张表。这通常用于全量数据导入、物理备份等需要保证全局一致性的场景。批量数据操作与大范围更新当执行全量数据导入、删除或者 UPDATE/DELETE 语句涉及表中绝大部分数据如超过 80%时优化器可能会认为逐行加锁的开销过高从而主动升级为表级锁以提高效率。数据仓库环境在隔离级别为可重复读RR且查询需要访问表中大部分数据时数据库自动放置表锁这比获取大量行锁或页锁效率更高特点开销小加锁快不会出现死锁锁定粒度大发出锁冲突的概率最高并发度最低。2.3.3 页级锁页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快但冲突多行级冲突少但速度慢。所以取了折衷的页级一次锁定相邻的一组记录。特点开销和加锁时间界于表锁和行锁之间会出现死锁锁定粒度界于表锁和行锁之间并发度一般2.4 锁分类按类型分从锁的类别上来讲有共享锁和排他锁这两种锁都属于行锁。2.4.1 共享锁Shared LocksS锁:又叫做读锁。 共享锁就是多个事物对于同一数据可以共享一把锁都能访问到数据但是只能读不能修改共享锁可以同时加上多个。2.4.2 排他锁Exclusive Locks X锁:又叫做写锁。 排他锁不能和其他锁共存如果一个事物获取了一个数据行的排它锁其他事物就不能再获取该行的其他锁包括共享锁和排它锁但是获取排他锁的事务是可以对数据就行进行读取和修改。对于UPDATE、DELETE和INSERT语句innodb会自动给涉及数据集加排它锁X。对于普通select语句innodb不会加任何锁。来分析一下获取锁的情形假如存在事务 A 和事务 B事务 A 获取了一条记录的 S 锁此时事务 B 也想获取该条记录的 S 锁那么事务 B 也能获取到该锁也就是说事务 A 和事务 B 同时持有该条记录的 S 锁。如果事务 B 想要获取该记录的 X 锁则此操作会被阻塞直到事务 A 提交之后将 S 锁释放。如果事务 A 首先获取的是 X 锁则不管事务 B 想获取该记录的 S 锁还是 X 锁都会被阻塞直到事务 A 提交。因此我们可以说 S 锁和 S 锁是兼容的 S 锁和 X 锁是不兼容的 X 锁和 X 锁也是不兼容的。2.4.3 意向锁意向共享锁Intention Shared Lock简称 IS 锁。当事务准备在某条记录上加 S 锁时需要先在表级别加一个 IS 锁。意向独占锁Intention Exclusive Lock简称 IX 锁。当事务准备在某条记录上加 X 锁时需要先在表级别加一个 IX 锁。意向锁是表级锁它们的提出仅仅为了在之后加表级别的 S 锁和 X 锁时可以快速判断表中的记录是否被上锁以避免用遍历的方式来查看表中有没有上锁的记录。就是说其实 IS 锁和 IS 锁是兼容的IX 锁和 IX 锁是兼容的。为什么需要意向锁InnoDB 的意向锁主要用户多粒度的锁并存的情况。比如事务A要在一个表上加S锁如果表中的一行已被事务 B 加了 X 锁那么该锁的申请也应被阻塞。如果表中的数据很多逐行检查锁标志的开销将很大系统的性能将会受到影响。举个例子如果表中记录 1 亿事务 A 把其中有几条记录上了行锁了这时事务 B 需要给这个表加表级锁如果没有意向锁的话那就要去表中查找这一亿条记录是否上锁了。如果存在意向锁那么假如事务在更新一条记录之前先加意向锁再加锁事务 B 先检查该表上是否存在意向锁存在的意向锁是否与自己准备加的锁冲突如果有冲突则等待直到事务释放而无须逐条记录去检测。事务更新表时其实无须知道到底哪一行被锁了它只要知道反正有一行被锁了就行了。说白了意向锁的主要作用是处理行锁和表锁之间的矛盾能够显示某个事务正在某一行上持有了锁或者准备去持有锁。表级别的各种锁的兼容性2.5 锁分类算法实现对于上面的锁的介绍我们实际上可以知道主要区分就是在锁的粒度上面而 InnoDB 中用的锁就是行锁也叫记录锁但是要注意这个记录指的是通过给索引上的索引项加锁。InnoDB 这种行锁实现特点意味着只有通过索引条件检索数据InnoDB 才使用行级锁否则InnoDB 将使用表锁。不论是使用主键索引、唯一索引或普通索引InnoDB 都会使用行锁来对数据加锁。只有执行计划真正使用了索引才能使用行锁即便在条件中使用了索引字段但是否使用索引来检索数据是由 MySQL 通过判断不同执行计划的代价来决 定的如果 MySQL 认为全表扫描效率更高比如对一些很小的表它就不会使用索引这种情况下 InnoDB 将使用表锁而不是行锁。同时当我们用范围条件而不是相等条件检索数据并请求锁时InnoDB 会给符合条件的已有数据记录的索引项加锁。不过即使是行锁InnoDB 里也是分成了各种类型的。换句话说即使对同一条记录加行锁如果类型不同起到的功效也是不同的。通常有以下几种常用的行锁类型。2.5.1 Record Lock记录锁单条索引记录上加锁。Record Lock 锁住的永远是索引不包括记录本身即使该表上没有任何索引那么innodb会在后台创建一个隐藏的聚集主键索引那么锁住的就是这个隐藏的聚集主键索引。记录锁是有 S 锁和 X 锁之分的当一个事务获取了一条记录的 S 型记录锁后其他事务也可以继续获取该记录的 S 型记录锁但不可以继续获取 X 型记录锁当一个事务获取了一条记录的 X 型记录锁后其他事务既不可以继续获取该记录的 S 型记录锁也不可以继续获取 X 型记录锁。2.5.2 Gap Locks间隙锁对索引前后的间隙上锁不对索引本身上锁。MySQL 在 REPEATABLE READ 隔离级别下是可以解决幻读问题的解决方案有两种可以使用 MVCC 方案解决也可以采用加锁方案解决。但是在使用加锁方案解决时有问题就是事务在第一次执行读取操作时那些幻影记录尚不存在我们无法给这些幻影记录加上记录锁。所以我们可以使用间隙锁对其上锁。如存在这样一张表CREATETABLEtest(idINT(1)NOTNULLAUTO_INCREMENT,numberINT(1)NOTNULLCOMMENT数字,PRIMARYKEY(id),KEYnumber(number)USINGBTREE)ENGINEINNODBAUTO_INCREMENT1DEFAULTCHARSETutf8;# 插入以下数据INSERTINTOtestVALUES(1,1);INSERTINTOtestVALUES(5,3);INSERTINTOtestVALUES(7,8);INSERTINTOtestVALUES(11,12);如下开启一个事务 ABEGIN;SELECT*FROMtestWHEREnumber3FORUPDATE;此时会对((1,1),(5,3))和((5,3),(7,8))之间上锁。如果此时在开启一个事务 B 进行插入数据如下为什么不能插入因为记录(2,2)要 插入的话在索引 number上刚好落在((1,1),(5,3))和((5,3),(7,8))之间是有锁的所以不允许插入。 如果在范围外当然是可以插入的如INSERTINTOtest(id,number)VALUES(8,8);2.5.3 Next-Key Locksnext-key locks 是索引记录上的记录锁和索引记录之前的间隙上的间隙锁的组合包括记录本身每个 next-key locks 是前开后闭区间也就是说间隙锁只是锁的间隙没有锁住记录行next-key locks 就是间隙锁基础上锁住右边界行。默认情况下InnoDB 以 REPEATABLE READ 隔离级别运行。在这种情况下InnoDB 使用 Next-Key Locks 锁进行搜索和索引扫描这可以防止幻读的发生。2.5.4 总结innodb对于行的查询使用next-key lockNext-locking keying为了解决Phantom Problem幻读问题当查询的索引含有唯一属性时将next-key lock降级为record keyGap锁设计的目的是为了阻止多个事务将记录插入到同一范围内而这会导致幻读问题的产生有两种方式显式关闭gap锁除了外键约束和唯一性检查外其余情况仅使用record lock A. 将事务隔离级别设置为RC B. 将参数innodb_locks_unsafe_for_binlog设置为12.6 乐观锁和悲观锁数据库管理系统DBMS中的并发控制的任务是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性。乐观并发控制乐观锁和悲观并发控制悲观锁是并发控制主要采用的技术手段。乐观锁和悲观锁其实不算是具体的锁而是一种锁的思想不仅仅是在 MySQL 中体现常见的 Redis 等中间件都可以应用这种思想。2.6.1 悲观锁假定会发生并发冲突屏蔽一切可能违反数据完整性的操作。在查询完数据的时候就把事务锁起来直到提交事务。实现方式使用数据库中的锁机制如共享锁或者排它锁2.6.2 乐观锁假设不会发生并发冲突只在提交操作时检查是否违反数据完整性。在修改数据的时候把事务锁起来通过version的方式来进行锁定。实现方式乐观锁一般会使用版本号机制或CAS算法实现2.6.3 两种锁的使用场景从上面对两种锁的介绍我们知道两种锁各有优缺点不可认为一种好于另一种像乐观锁适用于写比较少的情况下多读场景即冲突真的很少发生的时候这样可以省去了锁的开销加大了系统的整个吞吐量。但如果是多写的情况一般会经常产生冲突这就会导致上层应用会不断的进行retry这样反倒是降低了性能所以一般多写的场景下用悲观锁就比较合适。2.7 Mysql的加锁操作2.7.1 读操作的锁对于 MySQL 的读操作有两种方式加锁。SELECT * FROM table LOCK IN SHARE MODE如果当前事务执行了该语句那么它会为读取到的记录加 S 锁这样允许别的事务继续获取这些记录的 S 锁比方说别的事务也使用 SELECT … LOCK IN SHARE MODE 语句来读取这些记录但是不能获取这些记录的 X 锁比方说使用 SELECT … FOR UPDATE 语句来读取这些记录或者直接修改这些记录。如果别的事务想要获取这些记录的 X 锁那么它们会阻塞直到当前事务提交之后将这些记录上的 S 锁释放掉SELECT FROM table FOR UPDATE如果当前事务执行了该语句那么它会为读取到的记录加 X 锁这样既不允许别的事务获取这些记录的 S 锁比方说别的事务使用 SELECT … LOCK IN SHARE MODE 语句来读取这些记录也不允许获取这些记录的 X 锁比如说使用 SELECT … FOR UPDATE 语句来读取这些记录或者直接修改这些记录。如果别的事务想要获取这些记录的 S 锁或者 X 锁那么它们会阻塞直到当前事务提交之后将这些记录上的 X 锁释放掉。2.7.2 写操作的锁对于 MySQL 的写操作常用的就是 DELETE、UPDATE、INSERT。隐式上锁自动加锁解锁。DELETE对一条记录做 DELETE 操作的过程其实是先在 B树中定位到这条记录的位置然后获取一下这条记录的 X 锁然后再执行 delete mark 操作。我们也可以把这个定位待删除记录在 B树中位置的过程看成是一个获取 X 锁的锁定读。INSERT一般情况下新插入一条记录的操作并不加锁InnoDB 通过一种称之为隐式锁来保护这条新插入的记录在本事务提交前不被别的事务访问。UPDATE在对一条记录做 UPDATE 操作时分为三种情况如果未修改该记录的键值并且被更新的列占用的存储空间在修改前后未发生变化则先在 B树中定位到这条记录的位置然后再获取一下记录的 X 锁最后在原记录的位置进行修改操作。其实我们也可以把这个定位待修改记录在 B树中位置的过程看成是一个获取 X 锁的锁定读。如果未修改该记录的键值并且至少有一个被更新的列占用的存储空间在修改前后发生变化则先在 B树中定位到这条记录的位置然后获取一下记录的 X 锁将该记录彻底删除掉就是把记录彻底移入垃圾链表最后再插入一条新记录。这个定位待修改记录在 B树中位置的过程看成是一个获取 X 锁的锁定读新插入的记录由 INSERT 操作提供的隐式锁进行保护。如果修改了该记录的键值则相当于在原记录上做 DELETE 操作之后再来一次 INSERT 操作加锁操作就需要按照 DELETE 和 INSERT 的规则进行了。2.7.3 为什么上了写锁别的事务还可以读操作因为InnoDB有 MVCC机制多版本并发控制可以使用快照读而不会被阻塞。2.8 隔离级别与锁的关系在Read Uncommitted级别下读取数据不需要加共享锁这样就不会跟被修改的数据上的排他锁冲突在Read Committed级别下读操作需要加共享锁但是在语句执行完以后释放共享锁在Repeatable Read级别下读操作需要加共享锁但是在事务提交之前并不释放共享锁也就是必须等待事务执行完毕以后才释放共享锁。SERIALIZABLE 是限制性最强的隔离级别因为该级别锁定整个范围的键并一直持有锁直到事务完成。3. 数据库死锁死锁是指两个或多个事务在同一资源上相互占用并请求锁定对方的资源从而导致恶性循环的现象。例如说两个事务事务A锁住了1 ~ 5行同时事务B锁住了6 ~ 10行此时事务A请求锁住6 ~ 10行就会阻塞直到事务B施放6 ~ 10行的锁而随后事务B又请求锁住1 ~ 5行事务B也阻塞直到事务A释放1~5行的锁。死锁发生时会产生Deadlock错误。 锁是对表操作的所以自然锁住全表的表锁就不会出现死锁。3.1 产生的条件互斥条件一个资源每次只能被一个进程使用请求与保持条件一个进程因请求资源而阻塞时对已获得的资源保持不放不剥夺条件进程已获得的资源在没有使用完之前不能强行剥夺循环等待条件多个进程之间形成的一种互相循环等待的资源的关系。3.2 死锁排查正在运行的任务show full processlist; 找到卡住的进程解开死锁UNLOCK TABLES 查看当前运行的事务SELECT * FROM information_schema.INNODB_TRX;当前出现的锁SELECT * FROM information_schema.INNODB_LOCKS;观察错误日志查看InnoDB锁状态show status like innodb_row_lock%;lnnodb_row_lock_current_waits:当前正在等待锁定的数量;lnnodb_row_lock_time :从系统启动到现在锁定的总时间长度单位ms;Innodb_row_lock_time_avg :每次等待所花平均时间;Innodb_row_lock_time_max:从系统启动到现在等待最长的一次所花的时间;lnnodb_row_lock_waits :从系统启动到现在总共等待的次数。kill id 杀死进程3.3 常见的解决死锁的方法死锁无法避免上线前要进行严格的压力测试快速失败innodb_lock_wait_timeout 行锁超时时间拆分sql严禁大事务充分利用索引优化索引尽量把有风险的事务sql使用上覆盖索优化where条件前缀匹配提升查询速度引减少表锁无法避免时操作多张表时尽量以相同的顺序来访问避免形成等待环路单张表时先排序再操作使用排它锁 比如 for update如果业务处理不好可以用分布式事务锁或者使用乐观锁4. XA协议https://dev.mysql.com/doc/refman/8.0/en/xa.htmlAPApplication Program应用程序定义事务边界定义事务开始和结束并访问事务边界内的资源。RMResource Manger资源管理器: 管理共享资源并提供外部访问接口。供外部程序来访问数据库等共享资源。此外RM还具有事务的回滚能力。TMTransaction Manager事务管理器TM是分布式事务的协调者TM与每个RM进行通信负责管理全局事务分配事务唯一标识监控事务的执行进度并负责事务的提交、回滚、失败恢复等。4.1 流程应用程序AP向事务管理器TM发起事务请求TM调用xa_open()建立同资源管理器的会话TM调用xa_start()标记一个事务分支的开头AP访问资源管理器RM并定义操作比如插入记录操作TM调用xa_end()标记事务分支的结束TM调用xa_prepare()通知RM做好事务分支的提交准备工作。其实就是二阶段提交的提交请求阶段。TM调用xa_commit()通知RM提交事务分支也就是二阶段提交的提交执行阶段。TM调用xa_close管理与RM的会话。这些接口一定要按顺序执行比如xa_start接口一定要在xa_end之前。此外这里千万要注意的是事务管理器只是标记事务分支并不执行事务事务操作最终是由应用程序通知资源管理器完成的。另外我们来总结下XA的接口xa_start:负责开启或者恢复一个事务分支并且管理XID到调用线程xa_end:负责取消当前线程与事务分支的关系xa_prepare:负责询问RM 是否准备好了提交事务分支 xa_commit:通知RM提交事务分支xa_rollback:通知RM回滚事务分支4.2 mysql xa事务mysql的xa事务分为两部分InnoDB内部本地普通事务操作协调数据写入与log写入两阶段提交外部分布式事务5.7SHOWVARIABLESLIKE%innodb_support_xa%;8.0默认开启无法关闭XA 事务语法示例如下XASTART自定义事务id;SQL语句...XAEND自定义事务id;XAPREPARE自定义事务id;XACOMMIT\ROLLBACK自定义事务id;XA PREPARE 执行成功后事务信息将被持久化。即使会话终止甚至应用服务宕机只要我们将【自定义事务id】记录下来后续仍然可以使用它对事务进行 rollback 或者 commit。4.3 xa事务与普通事务区别xa事务可以跨库或跨服务器属于分布式事务同时xa事务还支撑了InnoDB内部日志两阶段记录普通事务只能在单库中执行4.4 什么是2pc 3pc两阶段提交协议与3阶段提交协议额外增加了参与的角色保证分布式事务完成更完善5. select for update流程查询库存1000扣减库存-199记录日志log 提交commitselect本身是一个查询语句查询语句是不会产生冲突的一种行为一般情况下是没有锁的用select for update 会让select语句产生一个排它锁(X), 这个锁和update的效果一样会使两个事务无法同时更新一条记录。https://dev.mysql.com/doc/refman/8.0/en/innodb-locks-set.htmlhttps://dev.mysql.com/doc/refman/8.0/en/select.htmlfor update仅适用于InnoDB且必须在事务块(BEGIN/COMMIT)中才能生效。在进行事务操作时通过“for update”语句MySQL会对查询结果集中每行数据都添加排他锁其他线程对该记录的更新与删除操作都会阻塞。排他锁包含行锁、表锁。InnoDB默认是行级别的锁在筛选条件中当有明确指定主键或唯一索引列的时候是行级锁。否则是表级别。示例SELECT…FORUPDATE[OFcolumn_list][WAIT n|NOWAIT][SKIP LOCKED];select*fromtforupdate会等待行锁释放之后返回查询结果。select*fromtforupdatenowait 不等待行锁释放提示锁冲突不返回结果select*fromtforupdatewait5等待5秒若行锁仍未释放则提示锁冲突不返回结果select*fromtforupdateskip locked 查询返回查询结果但忽略有行锁的记录参考https://blog.csdn.net/ThinkWon/article/details/104778621https://javatv.blog.csdn.net/article/details/121940259