事务控制
事务控制语句
MySQL默认设置下,事务都是自动提交的,即SQL执行后会立即执行commit
操作。
1)常用事务语句
显式的开启一个事务需要使用begin
、start transaction
或者执行set autocommit=0
,通过这些语句停止事务的自动提交。常用的事务控制语句如下,
语句 | 含义 |
---|---|
start transaction、begin | 显式开启事务 |
commit、commit work | 提交事务,对数据库的修改成为永久性的 |
rollback、rollback work | 回滚当前事务,撤销正在执行的所有未提交的改变 |
savepoint identifier | 设置一个标识符为identifier的保存点 |
release savepoint identifier | 删除指定的事务保存点,如果该保存点不存在会抛出异常 |
rollback to identifier | 回滚到指定保存点 |
set transaction | 设置隔离级别,InnoDB提供的隔离级别有4种,见锁笔记 |
2)差异辨析
- start transaction和 begin
两个语句都可以显式开启事务,区别在于存储过程。
在存储过程中,MySQL的分析器会将begin
识别为begin ... end
,因此在存储过程中只能使用start transaction
开启一个事务。 - commit和 commit work
都用于提交事务,不同之处在于commit work
用于控制事务结束后的行为是chain
还是release
,如果是chain
方式,事务就成了链事务。
可以通过设置completion_type
变量的值来进行控制,默认为0。在这种情况下,commit
和commit work
是等价的。
当该变量值为1时,commit work
等价于commit work chain
,表示立刻开启一个相同隔离级别的事务。
该变量值为2时,等价于commit work release
,事务提交后会自动断开与服务器的连接。
上方操作完成后,查询表中所有记录,只会得到a=1
的一条记录。
设置completion_type=1
,插入a=1
后对事务进行提交,插入重复记录2时抛出异常。之后执行rollback
操作,只留下一条记录,因为commit work chain
后自动开启一个链事务,第二条语句在同一个事务内,所以回滚后a=2
并没有插入到数据表中。
3)注意事项
已正确执行语句不会自动回滚
InnoDB的事务是原子的,构成事务的每条语句都会提交,或者所有语句都回滚。一条语句失败并抛出异常时,并不会导致先前已经执行的语句回滚。必须用户自己决定是否对已执行的语句提交或回滚,
重复插入值后,并没有将第一次正确执行的语句所做的改变回滚。
rollback to savepoint并非结束事务
rollback to savepoint
中虽然有 rollback,但是并不是真正结束事务。执行力rollback to savepoint
后也需要显式提交或回滚。
通过rollback to savepoint回滚到保存点t2
,事务并没有结束,
再运行 rollback后,事务才完整回滚。
隐式提交SQL语句
下方的SQL语句会产生一个隐式提交操作,即执行完这些语句后,会有一个隐式的commit
操作,
需要注意truncate table
语句是DDL,虽然和对整张表执行delete
操作是一样的,但truncate
无法回滚。
分布式事务
1)XA事务构成
分布式事务允许多个独立的事务资源参与到一个全局事务中。事务资源通常是关系型数据库系统。使用分布式事务时,InnoDB存储引擎的事务隔离级别必须设置为serializable
。
XA事务有一个或多个资源管理器、一个事务管理器和一个应用程序组成。
- 资源管理器:提供访问事务资源的方法,通常一个数据库就是一个资源管理器
- 事务管理器:协调全局事务中的各个事务,需要与所有资源管理器通信
- 应用程序:定义事务边界,指定全局事务中的操作
分布式事务使用两段式提交,
- 阶段1:所有参与全局事务的节点都开始准备(PREPARE),告诉事务管理器已做好提交准备
- 阶段2:事务管理器告知资源管理器执行 rollback 或 commit。任何一个节点如果不能被正常提交,则其余所有事务都要回滚。
相比于本地事务,分布式事务多了一次PREPARE操作,待收到所有节点的信息后,再进行提交或回滚。
2)XA事务语法
MySQL数据库XA事务的SQL语法如下,
单节点上运行XA事务的例子,
事务控制不良习惯
1)循环中提交
大多数情况下,MySQL都会开启自动提交,如果遇到循环执行的SQL语句,相当于每轮循环中都会进行一次提交。下面两个事务过程的执行时相同的,
- 自动提交开启的状态下,显式的在每轮循环中提交
- 自动提交开启状态下,未显式在每轮循环中提交
- 自动提交开启状态下,存储过程使用
start transaction
对上述三个过程进行调用,
相比于load1和load2中count
次提交,存储过程load3只进行了一次提交。如果对load2的调用过程进行调整也可以达到 load3的效果,
2)不关注一个事务中语句顺序
根据两阶段锁,整个事务中涉及的锁,需要等待事务提交时才会释放。同一个事务中,把没锁或锁定范围小的语句先执行,锁定范围大的语句后执行。
3)不关注不同事务访问资源的顺序
死锁原因中有两条与不同事务访问资源的顺序有关,
- 不同线程并发访问同一张表的多行数据,未按顺序访问导致死锁
- 不同线程并发访问多个表时,未按顺序访问导致死锁
4)不关注事务隔离级别
如果完全不关注业务使用的 MySQL 是什么隔离级别,可能会降低程序的并发能力或者导致死锁。
比如业务场景完全能接受幻读,如果要求更高的 QPS,使用 RR 隔离级别显然不是最好的选择,因此可以改为 RC 隔离级别。而如果业务使用的是 RR 隔离级别,可能由于间隙锁导致死锁(可参考MySQL锁笔记中的例子),因此也应该在程序编写时关注 RR 隔离级别下是否会有间隙锁。
5)混合使用存储引擎
在事务中混合使用事务型(比如 InnoDB)和非事务型(比如 MyISAM)表,如果是正常提交是什么问题。但是,如果该事务回滚了,事务型的表可以正常回滚,而非事务型的表的变更就无法回滚了。这种情况就会导致数据不正常,并且事务最终的结果也难以确定。