多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
**提到事务,我们都了解事务具有4个基本属性ACID,而通常我们不知道四个属性的实现,这里我们尝试对数据库的事务进行分析,尝试理解事务的实现。** **事务是并发控制的基本单位**。 ***** ACID四大特性是事务实现的基础,了解了ACID的实现,我们就清楚了事务的实现。 # 原子性 原子性保证了一组操作由多个多个子操作组成,这些子操作要么全部执行,要么全部不执行。 ![](https://box.kancloud.cn/e63344a17ea18c66ed3c10c73392a1b4_733x213.png) ## 日志回滚 为了实现原子性,在写入操作发生异常的时候,对前面已执行的更新操作进行**回滚**。 在MySQL中通过 **回滚日志(undo log)** 来实现。事务中所有的修改操作会先记录到这个回滚日志中,然后写入数据库中。 比如插入数据时,回滚日志中记录了insert into user (id) values (1); 回滚数据时需要执行 delete from user where id = 1; 回滚日志时实现了持久性。 ## 事务的状态 事务的状态只有3中,分别是:Active,Commited,Failed ![](https://box.kancloud.cn/e571893e360e390b1d26a1f144779a56_567x197.png) # 持久性 事务的持久性体现在:只要事务被提交,数据一定保存到数据库中(磁盘)。 ## 重做日志 MySQL通过 **重做日志(redo log)** 来实现事务的持久性。重做日志由两部分组成,分别是内存中的重做日志缓冲区,磁盘中的重做日志文件。 修改数据时的写入顺序如下 1. 从磁盘读取行记录写入内存 2. 内存中的行记录被更新 3. 更新日志写入重做日志缓冲区 4. 重做日志缓冲区的内容写入到重做日志文件 5. 内存中的行记录更新到磁盘 > 第4,5步在事务 commit 时执行。 重做日志以512字节的块形式保存,跟磁盘扇区大小一致,保证了重做日志写入磁盘的原子性。 ![](https://box.kancloud.cn/363bd1eea3c32aeedc28ff662c58e4d9_760x302.png) ## 原子性和一致性 回滚日志保证了发生错误或者需要回滚的事务能够被成功回滚。 重做日志保证了对于已提交事务的修改能够写入数据库,发生意外宕机时,从重写日志恢复磁盘数据。 # 隔离性 > 用来描述多个事务之间的关系。 **事务并行**产生了脏读,不可重复读,幻读问题。 隔离性是以上问题的解决方案。隔离性决定了一个事务里的修改,哪些内容在其他事务里是可见的。 隔离级别是隔离性的具体实现策略,分别是: 1. read uncommited (脏读) 2. read commited (解决了“脏读“,存在“不可重复读“) 3. repeatable read (解决了“不可重复读“,产生了“幻读“) 4. serializable ![](https://box.kancloud.cn/0291e9930b631cdf9b766a57e10e77d6_851x164.png) ## 脏读 > 在一个事务中,读取了其他事务未提交的数据 当隔离级别是read uncommited时,session 2 中插入的未提交数据,在session1中可以被访问 ![](https://box.kancloud.cn/5723f3efb5d965b3a0d61a8bee2b0eeb_845x372.png) ## 不可重复读 > 在同一个事务中,一行记录的两次查询结果不一致 当隔离级别是read commited的情况下,session1中前后两次查询的结果不一致。 不可重复读发生的原因是,存储引擎不会在查询数据时添加行锁,锁定id=3这条记录。 ![](https://box.kancloud.cn/d6ae1e17204374502dee74dfe8c36df8_844x427.png) ## 幻读 > 在同一个事务中,读取了指定范围的数据集之后,另一个事务往该范围内插入了新数据 由于repeatable read的原因,session1中的两次查询得到了同样的结果,但是插入数据时返回错误 ![](https://box.kancloud.cn/7966fdfc447dbf5908bcea0a90ad7389_857x482.png) ## 隔离性(隔离级别)的实现 隔离级别是对隔离性的实现,限制同一时刻对共享资源的操作。 隔离级别的实现方式有 1. 共享锁 & 互斥锁 2. 多版本控制,支持数据被事务更新时对旧版本数据的访问,显著提高了读性能。 ## Next-Key解决幻读问题 ![](https://box.kancloud.cn/8cc4daa290d181a7a4b68d365f6a8f49_876x418.png) 当我们更新索引列age时,比如 `SELECT * FROM users WHERE age = 25 FOR UPDATE;`,InnoDB不仅锁定了 (21,25] 区间,还锁定了 (25, 30] 区间。确保其他事务无法插入age=25的数据。 ## 多版本并发控制 为了提升了“重复读“的读性能。 InnoDB维护了一个版本号,每启动一个事务,版本号就加一。表中添加两列,表示“创建时间“和“过期时间“,用来保存版本号。 CURD操作对这两列的维护策略如下 * insert:写入创建时间 * update:更新创建时间,写入过期时间 * select:创建时间小于等于当前事务的版本号 * delete:写入过期时间 ## 小结 隔离级别“提交读“解决了“脏读“问题 隔离级别“重复读“解决了“不可重复读“问题 隔离级别“串行化“解决了“幻读“问题 但由于“串行化“的性能问题,innodb通过Next-key来解决幻读问题。 多版本并发控制提升了“重复读“读性能 ## 参考资料 [『浅入深出』MySQL 中事务的实现](https://draveness.me/mysql-transaction) [浅谈数据库并发控制 - 锁和 MVCC](https://draveness.me/database-concurrency-control)