MySQL 事务

官网保平安:https://www.mysql.com/
MySQL 思维导图:https://www.processon.com/view/link/63bc2c8ea82ed9463ba99f38

MySQL 支持事务吗

在缺省模式下,MySQL 是 autocommit 模式的,所有的数据库更新操作都会即时提交,所以在缺省情况下,MySQL 是不支持事务的。

如果你的 MySQL 表类型是 InnoDB 或 BDB 的话,你的 MySQL 就可以使用事务处理,使用 SET AUTOCOMMIT=0(或者 BEGIN) 就可以使 MySQL 开启非 autocommit 模式,在非 autocommit 模式下,你必须使用 COMMIT 来提交你的更改,或者用 ROLLBACK 来回滚你的更改。

MySQL 开启事务

1
2
3
4
5
SET AUTOCOMMIT;  -- 启动事务,或者使用命令 BEGIN, SET AUTOCOMMIT 和 BEGIN 两者的作用一摸一样; 
UPDATE FROM account SET money=money-100 WHERE name='a';
UPDATE FROM account SET money=money+100 WHERE name='b';
ROLLBACK; -- 回滚
COMMIT; -- 手动关闭事务

什么是事务 ★★★

事务:是由一组 SQL 语句组成的逻辑处理单元,通常简称为事务。事务具有以下4个属性(ACID):

  1. 原子性(Atomicity):
    事务是一个原子操作单元,事务包含的所有操作要么全部成功,要么全部失败回滚。因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。

  2. 一致性(Consistent):
    在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性;事务结束时,所有的内部数据结构(如 B+Tree 索引或双向链表)也都必须是正确的。
    一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
    例如转账,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。

  3. 隔离性(Isolation):
    数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然。
    隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
    即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。

  4. 持久性(Durable):
    持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。MySQL 中会保存有相应的操作日志,即使遭遇故障依然能够通过日志恢复最后一次更新。
    例如我们在使用JDBC操作数据库时,在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务以及正确提交,即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成,否则就会造成我们看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误。

简单理解:事务由一个或多个sql语句组成一个整体;在事务中的操作,要么都执行修改,要么都不执行,只有在该事务中所有的语句都执行成功才会将修改加入到数据库中,否则回滚到上一步。

这里要额外补充一点:只有保证了事务的持久性、原子性、隔离性之后,一致性才能得到保障。也就是说 A、I、D 是手段,C 是目的! – 来自周志明老师的公开课《周志明的软件架构课》

MySQL 是如何确保 ACID 的

  • A 原子性:事务中的一组 SQL 语句要么全部执行成功,要么全部撤销,退回到执行事务之前的状态。通过Undo Log实现。
  • I 隔离性:事务有四种隔离级别:未提交读,提交读,可重复读和串行化。MySQL 的默认隔离级别是可重复读。MySQL 通过 MVCC + next-key Lock 实现。
  • D 持久性:在发生故障时,尚有脏页未写入磁盘,在重启 MySQL 的时候,根据 Redo log 进行重做,从而达到事务持久性。

MySQL 的事务隔离级别 ★★★

  • 未提交读(Read Uncommitted, RU):允许脏读,其他事务只要修改了数据,即使未提交,本事务也能看到修改后的数据值。也就是可能读取到其他会话中未提交事务修改的数据。
  • 提交读(Read Committed, RC):只能读取到已经提交的数据。
  • 可重复读(Repeated Read, RR):可重复读。无论其他事务是否修改并提交了数据,在这个事务中看到的数据值始终不受其他事务影响。
  • 串行读(Serializable):完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞。

MySQL 数据库(InnoDB引擎)默认使用可重复读 RR。
Oracle 等多数数据库默认都是提交读 RC。

事务的并发问题 ★★★

  • 脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据。
  • 不可重复读:事务A多次读取同一数据,事务B在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致。
  • 幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。

小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表。

事务隔离级别 脏读 不可重复读 幻读
读未提交(read-uncommitted)
不可重复读(read-committed)
可重复读(repeatable-read)
串行化(serializable)

不可重复读和幻读有什么区别

  • 不可重复读的重点是内容修改或者记录减少比如多次读取一条记录发现其中某些记录的值被修改;
  • 幻读的重点在于记录新增比如多次执行同一条查询语句(DQL)时,发现查到的记录增加了。

幻读其实可以看作是不可重复读的一种特殊情况,单独把幻读区分出来的原因主要是解决幻读和不可重复读的方案不一样。

举个例子:执行 deleteupdate 操作的时候,可以直接对记录加锁,保证事务安全。而执行 insert 操作的时候,由于记录锁(Record Lock)只能锁住已经存在的记录,为了避免插入新记录,需要依赖间隙锁(Gap Lock)。也就是说执行 insert 操作的时候需要依赖 Next-Key Lock(Record Lock+Gap Lock) 进行加锁来保证不出现幻读。

解决幻读的方法

解决幻读的方式有很多,但是它们的核心思想就是一个事务在操作某张表数据的时候,另外一个事务不允许新增或者删除这张表中的数据了。解决幻读的方式主要有以下几种:

  1. 将事务隔离级别调整为 SERIALIZABLE
  2. 在可重复读的事务级别下,给事务操作的这张表添加表锁。
  3. 在可重复读的事务级别下,给事务操作的这张表添加 Next-key Lock(Record Lock+Gap Lock)

MySQL 事务
https://flepeng.github.io/041-MySQL-41-底层原理-MySQL-事务/
作者
Lepeng
发布于
2020年8月8日
许可协议