深入理解mysql事务

做为开发人员对数据库事务应该都不陌生,但是如果知其然而不知其所以然的话,在开发中难免写出来的代码存在bug,本文主要介绍mysql中的事务,重点讲解事务的隔离级别。

1. ACID

1.1 原子性

原子性是指事务是一个不可分割的工作单位,事务中的操作要么全部执行,要么全部都不执行。
例如:
begin // 开启事务
A:update user set account=account+1 where id =1;
B:update user set account=account+1 where id =1;
commit
这个事务,执行commit时,在么两条语句都执行成功,如果出错,执行rollback时,两条语句的操作都会回滚到原始状态;

undo log保证原子性
在操作任何数据之前,首先将数据备份到一个地方(这个存储数据的地方就是undo log)。然后进行数据的修改,如果用户出现了错误或者用户执行了rollback语句,系统可用利用undo log中的备份的数据恢复到事务开始之前的状态。
注意:undo log是逻辑日志
可以理解为:

  • 当delete一条记录时,undo log中记录一条对应的insert记录
  • 当insert 一条记录时,undo log中会记录一条对应的delete记录
  • 当update一条记录时,它记录一条对应相反的udpate记录

    1.2 一致性

    事务执行前和事务执行后,数据库的完整性约束不被破坏。即事务的执行是从一个有效状态转移到另一个有效状态;
    例如:
    tom给jack转账50元,如果从tom账户减少后系统故障或其它原因没有给jack加上50元,而事务还没有执行完毕,数据库会将整个转账过程回滚,保证数据的一致性;

    1.3 隔离性

    隔离性是指在并发操作中,不同事务之间应该隔离开来,使每个并发中的事务不会互相干扰;

    1.4 持久化

    一旦事务提交成功,事务中所有的数据操作都必须被持久化保存到数据库中,即使提交事务后,数据库崩溃,在数据库重启时,也必须能保证通过某种机制恢复数据。
    redo log保证持久性
    redo log记录的是新数据的备份,在事务提交前,只要将redo log持久化即可,不需要将数据持久化。当系统崩溃时,虽然数据没有持久化,但是redo log已经持久化,系统可以根据redo log的内容,将所有数据恢复到最新的状态。

    2. 事务引起的问题

    上面介绍了数据库的事务特性,其中隔离性,是我们本文中重点需要解说的,设置不同的数据库事务之间的隔离级别,会解决相应的事务并发问题。那么下面说一下事务都会引起哪些问题:

2.1 脏读

事务A读取到了事务B已经修改但尚未提交的数据,如果事务B回滚,A读取的数据与上次不一致;不符合事务的一致性要求;
例如:A(图左)和B(图右)同时开启事务,A执行如下命令:
深入理解mysql事务
用户A并未提交事务,可见在事务B中两次查询的结果已经发生变化,读取了事务A中未提交的数据;

2.2 不可重复读

事务A读取到了事务B已经提交的修改数据,不符合隔离性;
深入理解mysql事务
上图可见,事务B读取到了事务A所修改的数据;

2.3 幻读

事务A读取到了事务B提交的新增数据,不符合隔离性;
深入理解mysql事务
事务B读取到了事务A提交的新增数据;

3. 深入隔离级别

隔离级别等级:
深入理解mysql事务

查看隔离级别:

show variables like '%isolation%';
SELECT @@GLOBAL.transaction_isolation, @@transaction_isolation;

设置隔离级别:

全局:SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;
会话:SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;

3.1 读未提交

读未提交是数据库事务隔离级别最低的级别,它任何问题都没有解决;
设置数据库事务隔离级别为读未提交

SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

深入理解mysql事务

3.2 读已提交

读已提交它主要解决脏读的问题;
设置数据库事务隔离级别为读已交

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
深入理解mysql事务
可以看出,读已提交已经解决了脏读的问题;

3.3 可重复读

设置事务隔离级别:

SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;

深入理解mysql事务
可以看到,就算事务A已经提交,事务B也不会再读到事务A提交的数据;
那么可重复读是否解决了幻读呢?
我们试验一下:
深入理解mysql事务
这是怎么回事呢?不是说好的可重复读不会解决幻读的问题吗?为什么这里新插入的数据就是没有在事务B中读取到呢? (下节解答)

3.4 串行化

串行化就不多说了,它其实是将事务按排队的处理方式,但是这样会使事务非常低率的。

4. mysql的MVCC

我们先来看个示例:
深入理解mysql事务
从上面的执行过程可以看出,在A事务提交之后,B事务虽然执行查询时没有 问题,但是在B事务执行更新之后,再查询时,账户直接少了100;这是为什么呢?

1) MVCC原理

InnoDB的MVCC通过每行记录后面的两个隐藏字段来实现的,创建时的版本号和删除时的版本号。每个事务开始版本号都会递增。
SELECT:

  • 查找版本早于当前事务版本的数据行,即,行的系统版本号小于或等于事务的系统版本号,这样可以确保事务读取的行,是在事务开始之前已经存在的或者自身事务操作过的。
  • 行的删除版本,要么未定义,要么大于当前事务版本号,这样可以确保事务读取到的行,在事务开始之前未被删除;
    INSERT:
    为插入的每一行保存当前系统版本号作为行的版本号;
    DELETE:
    删除的每行保存当前系统版本号作为删除标记;
    UPDATE:
    插入一行新记录,保存当前系统版本号为行版本号,同时,保存当前系统版本号到原来的行的删除标识;

2) 快照读和当前读

快照读:
读取的是快照版本,也就是历史版本;普通的select就是快照读
当前读:
读取的是最新版本,update、delete、insert、select...lock in share mode、select ... for update是当前读;

3.3节疑惑解答
事务A执行更新之后,提交了事务,而事务B再执行更新的时候,它其实是一个当前读,能够读取到最新的已经被事务A修改后的数据(前提事务A已经提交)。

3) undo log中的版本链及ReadView

这块内容比较复杂,可以参考:
https://blog.csdn.net/shenchaohao12321/article/details/92801779

猜你喜欢

转载自blog.51cto.com/13733462/2453460