MySQL 的事务
文章目录
- MySQL 的事务
- 什么是数据库事务?MySQL 事务的四大特性?
- 并发事务会出现什么问题?
- MySQL 的事务隔离级别?
- MVCC 实现原理?
- 核心原理
- MVCC 如何工作?
- MVCC 的优势
- MVCC 与隔离级别
- MVCC 的局限性
- 幻读是如何解决的?
- 读提交怎么实现?
- 什么情况下会发生事务回滚?如何手动回滚事务?
- 如何优化长事务的性能问题?
- 解释 MySQL 中 AUTOCOMMIT 的作用,以及它与事务的关系
通过本篇文章,对 MySQL 当中事务部份的知识点进行复习,主要的参考资料是 csview,原文链接如下:https://www.csview.cn/mysql/transaction.html。
什么是数据库事务?MySQL 事务的四大特性?
数据库的事务是指作为单个逻辑工作单元执行的一系列操作,要么全部执行成功,要么全部不执行。
MySQL 中事务的四大特性(ACID)指:
- 原子性(Atomicity):事务的原子性是指,整个事务当中的数据库操作要么全部成功,要么全部失败,不可能仅执行其中的一部分。
- 一致性(Consistency):数据库总是从一个一致性的状态迁移到另一个一致性的状态。
- 隔离性(Isolation):一个事务所做的修改在最终提交之前,对其他事务是不可见的。
- 持久性(Durability):一旦事务提交,则其所做的修改就会永远保存到数据库当中持久化。此时即使系统崩溃,修改的数据也不会丢失。
并发事务会出现什么问题?
- 脏读:脏读指的是当前事务读到了其他事务未提交的数据,未提交意味着其他事务可能回滚,导致这批被其他事务修改的数据不会被最终持久化到数据库当中,也就是不存在的数据。读到了最终不一定存在的数据,就是脏读。
- 不可重复读:指的是在同一事务内,不同时刻读到的同一批数据可能是不一样的,可能会受其他事务的影响,比如其他事务修改了数据并提交。
- 可重复读:指的是在同一事务内,最开始读到的数据和事务结束前任意时刻读到的同一批数据是一致的。
- 幻读:指的是在同一个事务内,前后两次相同的查询返回了不同的行集合,因为在此期间有其他事务插入了新的行。幻读通常发生在范围查询当中。
MySQL 的事务隔离级别?
重要‼️
- 读未提交(read uncommitted):当一个事务还未提交时,它所做的修改已经可以被其他事务所看到。这种隔离级别不满足事务的隔离性,会产生脏读、不可重复读和幻读问题。
- 读提交(read committed):指事务提交之后,它所做的修改才会被其他事务看到。会产生不可重复读和幻读问题。
- 可重复读(repeatable read):可重复读是 MySQL InnoDB 引擎的默认隔离级别。具体来说,可重复读指的是一个事务的执行过程中所看到的数据,一直跟这个事务启动时看到的数据是相同的。在该隔离级别下,仍然可能发生幻读,但是不会出现不可重复读或脏读。
- 串行化(serializable):对记录加上读写锁,在多个事务对某条记录进行读写操作时,如果发生了读写冲突,那么后访问的事务必须等待前一个事务执行完毕才能够继续执行。串行化隔离级别下不会发生脏读、幻读以及不可重复读。不可能发生幻读的原因是,串行化隔离级别会对事务 A 的范围查询加范围锁,使得另一个事务 B 不能在 A 范围查询的区间内插入新的数据,故事务 A 第二次对这个区间进行范围查询时,不会读取到不同的数据,从而解决了幻读。
MVCC 实现原理?
MVCC(Muti-Version Concurrency Control,多版本并发控制)是 MySQL 中 InnoDB 存储引擎实现的一种并发控制机制,它通过保存数据的多个版本来实现高效的读写并发,不会相互阻塞。
核心原理
数据版本快照:
- 每行数据都有多个版本;
- 每个事务看到的都是其在开始时已经提交的快照数据;
隐藏的系统字段:
DB_TRX_ID
:6 字节,记录最近修改该行数据的事务 ID;DB_ROLL_PTR
:7 字节,回滚指针,指向该行的上一个版本;DB_ROW_ID
:6 字节,隐藏的自增行 ID。
ReadView 机制
- 事务开始时创建一个 ReadView;
- 包含:当前活跃事务 ID 列表、最小事务 ID、下一个将分配的事务 ID。
MVCC 如何工作?
读操作(SELECT)
- 检查数据行的
DB_TRX_ID
; - 如果数据行的
DB_TRX_ID
小于 ReadView 中的最小活跃事务 ID,说明数据已经提交,可见; - 如果数据行的
DB_TRX_ID
在活跃事务列表中,说明数据未提交,不可见; - 如果数据行的
DB_TRX_ID
大于等于下一个将分配的事务 ID,说明这行数据是由未来事务创建的,不可见。
写操作(INSERT/UPDATE/DELETE)
- INSERT:新插入的行
DB_TRX_ID
设置为当前事务 ID; - DELETE:标记为删除,但并不真的删除,
DB_TRX_ID
设置为当前事务 ID; - UPDATE:创建新版本,旧版本通过
DB_ROLL_PTR
指向 undo log。
MVCC 的优势
读写不阻塞
- 读操作不需要等待写操作完成;
- 写操作不需要等待读操作完成;
提高并发性能
- 多个事务可以同时读取同一数据的不同版本;
- 避免了大量的锁等待;
支持快照读
- 确保事务内看到一致的数据视图。
MVCC 与隔离级别
读未提交:不使用 MVCC,直接读取最新的数据;
读已提交:
- 每次读取都生成新的 ReadView;
- 能看到其它事务已经提交的修改;
可重复读:
- 事务开始时生成 ReadView,整个事务期间使用同一个 ReadView;
- 保证可重复读;
串行化:退化为基于锁的并发控制;
MVCC 的局限性
- 额外存储开销:需要存储多个版本的数据;
- 需要定期清理:需要 purge 县城清理不再需要的就版本;
- 写冲突仍需锁:写操作仍然需要获取排他锁。
幻读是如何解决的?
快照读(普通 select 语句):针对普通 select 语句,可以通过 MVCC 机制解决幻读问题。具体来说,在可重复读隔离级别下,事务执行过程中可以看到的数据,一直和事务刚刚启动时的数据是一致的,如果中途有其他事务插入了一条数据,插入的记录的 DB_TRX_ID
要么是处于活跃事务列表当中的(还未被提交),要么是大于等于下一个将要被分配的事务的 ID 的(代表这个事务在当前事务之后创建并且已经执行完毕),因此当前事务查询不到这条新插入的数据。
当前读(select … for update 等语句):在可重复读隔离级别下,InnoDB 通过 next-key lock(记录锁 + 间隙锁,也称临键锁)方式解决幻读。记录锁用于锁定索引中的具体记录,间隙锁用于锁定索引记录之间的间隙(也就是区间),防止新记录的插入(新的记录无法被插入到加了临键锁的区间当中)。
需要注意的是,对于快照读和当前读,都没有彻底解决幻读。即 MySQL 的可重复读隔离级别只是在极大程度上避免了幻读现象的发生。
读提交怎么实现?
读提交隔离级别是在每次提取数据时,都会生成一个新的 Read View。事务期间如果多次读取同一条数据,前后两次读的时候可能会出现不一致,因为在此期间可能有另一个事务修改了这条记录,并提交了事务。
什么情况下会发生事务回滚?如何手动回滚事务?
- 显式调用
ROLLBACK
; - 出现错误且未被捕获(比如 dead lock、唯一键冲突等);
- 连接中断且未启用自动提交;
如何优化长事务的性能问题?
优化长事务的参看方法如下:
- 拆分事务:将事务拆分为若干个小事务;
- 减少锁定范围:只锁定必要的数据,尽可能避免全表扫描;
- 合理设置隔离级别:根据业务需求选择最低可用的隔离级别;
- 监控长事务;
- 设置超时;
解释 MySQL 中 AUTOCOMMIT 的作用,以及它与事务的关系
AUTOCOMMIT 是 MySQL 的一个会话变量(默认为 ON),控制是否自动提交每条 SQL 语句:
AUTOCOMMIT == 1
时,每条 SQL 作为一个独立事务自动提交;AUTOCOMMIT == 0
时,需要显式调用 COMMIT 或 ROLLBACK;
与事务的关系:
- 即使启用了 AUTOCOMMIT,也可以使用
START TRANSACTION
显式开启事务; - 显式事务会临时禁用 AUTOCOMMIT 直到事务结束;
- 设置 AUTOCOMMIT 为 0 时,会话会一直处于事务中直到显式提交。