探索MySQL的事务机制

事务

简单来说,事务就是要保证一组数据库操作,要么全部成功,要么全部失败。MySQL中事务支持是在引擎层实现的。事务拥有四个重要的特性:原子性、一致性、隔离性、持久性,简称为ACID特性,下文将逐一解释。

ACID特性

  • 原子性(Atomicity)
    • 事务开始后所有操作,要么全部完成,要么全部不做,不存在只执行一部分的情况。
  • 一致性(Consistency)
    • 事务将数据库从一种状态转变为另一种一致的状态,数据库的完整性约束没有被破坏。
  • 隔离性(Isolation)
    • 在一个事务未执行完毕时,其它事务无法读取该事务的数据。
    • MySQL通过锁机制来保证事务的隔离性。
  • 持久性(Durability)
    • 事务一旦提交,数据将被保存下来,即使发生宕机等故障,数据库也能将数据恢复。
    • MySQL使用redo log来保证事务的持久性。

并发处理带来的问题

丢失更新

丢失更新就是两个事务在并发下同时进行更新,后一个事务的更新覆盖了前一个事务更新的情况。基本情景如下表:

时间 事务A 事务B
1 开启事务 开启事务
2 a = 100 a = 100
3 / a -= 10
4 / commit
5 / /
6 a += 20 /
7 commit /

脏读

一个事务读取了另一个未提交的事务写的数据,被称为脏读。基本情景如下:

时间 事务A 事务B
1 开启事务 开启事务
2 a = 100 a = 100
3 / a = 110
4 a = 110 /
5 / rollback

不可重复读

一个事务读取到另一个事务已经提交的修改数据。基本情景如下:

时间 事务A 事务B
1 开启事务 开启事务
a = 100 a = 100
3 / a = 110
4 a = 110 commit

幻读

一个事务按查询条件检索数据,发现其它事务插入了满足条件的数据,这被称为幻读。基本情景如下:

时间 事务A 事务B
1 开启事务 开启事务
2 select * from table where id = 1(记录为0条) /
3 / insert into table (id) value(1)
4 / commit
5 insert into table (id) value(1) /
6 commit(报错,主键冲突) /

隔离级别

由于数据库在并发事务中带来一些问题,数据库提供了事务隔离机制来解决相对应的问题。数据库的锁也是为了构建这些隔离级别而存在的。

隔离级别 脏读(Dirty Read) 不可重复读(NonRepeatable Read) 幻读(Phantom Read)
未提交读 可能 可能 可能
已提交读 不可能 可能 可能
可重复读 不可能 不可能 可能
可串行化 不可能 不可能 不可能
  • 未提交读(Read Uncommitted):一个事务还没提交时,它的修改能被别的事务看到。
  • 提交读(Read Committed):一个事务提交后,它的修改才能被其它事务看到。
  • 可重复读(Repeated Read):一个事务执行过程中看到的数据,总是跟这个事务开始时刻是一致的。
  • 可串行化(Serializable):对于同一行记录,写会加写锁,读会加读锁,当出现读写冲突的时候,后面的事务必须等待前一个事务执行完成才能继续执行。

查询与修改隔离级别的SQL语句:

1
2
3
4
5
6
7
8
-- 查看系统隔离级别:
select @@global.tx_isolation;
-- 查看当前会话隔离级别
select @@tx_isolation;
-- 设置当前会话隔离级别
SET session TRANSACTION ISOLATION LEVEL serializable;
-- 设置全局系统隔离级别
SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

事务隔离的实现

在MySQL中,实际上每条记录在更新的时候都会同时记录一条回滚操作。记录上的最新值,通过回滚操作,可以得到之前一个状态的值。假设一个值从1被按照顺序改成了2,3,4,在回滚日志中会有类似下面的记录。

记录

当前值是4,但是在查询这条记录的时候,不同时刻启动的事务会有不同的read-view,在视图A、B、C中,记录的值分别为1、2、4。同一条记录在系统中可以存在多个版本,就是数据库的多版本并发控制(MVCC )。对于read-view A要得到1,就必须将当前值依次执行图中所有的回滚操作得到。

同时你会发现,即使现在有另外一个事务正在将 4 改成 5,这个事务跟 read-view A、B、C对应的事务是不会冲突的。

系统会自动判断,如果没有事务需要用到这些回滚日志时(当系统里没有比这个回滚日志更早的read-view的时候),回滚日志会被删除。

通过上文可以发现,尽量不要使用长事务。长事务意味着系统里面存在着很老的事务视图。由于这些事务随时可能访问数据库里面的任何数据,所以这个事务提交之前,数据库里面它可能用到的回滚记录都必须保留,这会导致占用大量的存储空间。

文章作者: L1nker4
文章链接: https://l1n.wang/2020/04/mysql-transaction/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 L1nker4's Blog