MySQL事务

什么是事务

事务指的是当 DML 数据修改语句提交给数据库后,要么数据全部成功写入、如若其中某项操作失败则所有数据全部回滚到修改前状态的机制。数据库通过事务保证数据的完整性、一致性。

ACID

一个完整的事务,必然包含了如下4个特性:原子性、一致性、隔离性、持久性。

类别 描述
原子性(Atomicity) 事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
一致性(Consistency) 事务应确保数据库的状态从一个一致状态转变成另一个一致状态。一致状态的含义是数据库应满足完整性约束。
隔离性(Isolation) 多个事务并发执行时,一个事务的执行不影响其他事务的执行
持久性(Durability) 已被提交的事务对数据库的修改应该永久保存在数据库中。

多线程并发事务问题

在多线程环境中,对于同一条数据而言可能有多个线程同时在进行修改操作,即带来了操作冲突。按照并发控制理论,要想解决冲突的问题,可以有如下两种思路:

  1. 避免冲突发生,如串行化,加锁等;
  2. 允许冲突发生,即允许数据有多个版本,如使用MVCC

同时,多线程事务还存在隔离性的问题,当多个事务并发执行的时候,一个事务中能否感知到另外一个事务中的数据修改。数据库理论中专门定义了一系列的术语来描述:

术语 简介
脏读 一个事务可以读到另外一个事务中未提交的数据。这往往会造成数据不一致
不可重复读 同一事务中两次读取同一行,数据不一致的情况称为不可重复读
幻读 同一事务中通过统计或其他汇总语句统计出来的数据不一致的情况

为解决上述问题,数据库提出了事务隔离级别来与之对应。从下表可以看出,一致性越好,并发能力越差。

隔离级别 脏读 不可重复读 幻读
读未提交(Read Uncommit)
读已提交(Read Commit)
可重复读(Read Uncommit)
串行化(Read Uncommit)

InnoDB 事务

在计算机领域,数据一致性、与数据崩溃恢复通常是通过 WAL(Write Ahead Log) 技术来实现的——用户如果对数据库中的数据就行了修改,必须保证日志先于数据落盘。在InnoDB 引擎中,通过binlog 与redolog 的两段提交的方式(在事务执行前先写redolog,执行结束前写binlog)保证了数据的一致性和完整性,如若数据刷盘的过程中发生了异常,那么当MySQL重启的时候,根据日志对数据进行恢复,就可以还原数据,保证数据的一致性。在MySQL中,日志文件是最重要的数据,而数据文件反而没有那么重要。

MVCC 机制

InnoDB 引擎是一个多版本存储引擎,它通过保留数据行的旧版本来实现事务。

快照读与当前读

MVCC 中,数据有两种读取形式:

  • 快照读:读取的只是当前事务的可见版本,不用加锁。Select 使用的是快照读,因此查询性能可以获得极大的提高。
  • 当前读:读取的是当前版本,需要加锁,Insert、Update、Delete 使用的是当前读。

Undo Log

旧版本数据行称作undo日志,这些信息保存在表空间的回滚段里。通常我们的undo日志被用来做两件事:

  • 回滚
  • 一致性读

undo log 是逻辑上的日志,记录当前事务操作前的该行数据的原始状态。 可分为 insert undo logupdate undo log

类型 说明
Insert Undo Log 插入undo log在数据插入的时候产生,仅仅在回滚的时候才会被用到,因为该行是新增的,之前并不存在该数据,因此不存在多版本,可以在事务提交后就可以丢弃。
Update Undo Log 更新undo log 不仅在回滚时候需要用到,由于其他事务中可能用到了该数据,因此也需要用于一致性读。有可能其他事务也正在处理这条数据,因此只有当该条数据没有事务的时候,才能丢弃这些日志。因此我们需要关注事务的处理时长,如果一个事务长时间得不到释放那么对应的undolog日志就不能够被清除,当吞吐量较大的时候需要占用较大的表空间

数据行格式

InnoDB 引擎会隐性的为每一行数据都增加如下三个隐藏字段。InnoDB 通过这三个字段的相互配合实现了事务。数据行的格式如下:

DB_ROW_ID DB_TRX_ID DB_ROW_PTR 数据

数据行ID 6字节 上次事务ID,6字节 回滚指针,7字节

  • DB_ROW_ID 即行主键,6个字节,唯一标识一行数据,如若用户没有定义主键,InnoDB内部会自动创建一个行ID当作主键。

  • DB_TRX_ID 最后操作事务ID,6个字节,记录最后一次操作改行数据的事务ID

  • DB_ROW_PTR 回滚指针,指向回滚段中一行 undo 日志。

Delete过程

在InnoDB 中,删除操作实际上是被当作一个更新操作来处理。当我们需要删除一条数据的时候,首先InnoDB在内存中将该数据的删除标识位以及其辅助索引删除标识位都标识为已删除,之后当该数据的undo log 可以被清除的时候,InnoDB 通过purge线程将该数据物理清除。

同样InnoDB 的物理数据删除也是一个逻辑删除过程,它仅仅是将该数据行标记为已删除,仅此而已。数据标记为删除并不意味着该数据行所拥有的空间能够立即被复用,后续可以被用到的时候, 该数据列才会被覆盖掉。因此当我们通过delete 方法删除所有数据的时候,我们可以看到.ibd文件的大小并没有减少。这种索引列可以复用却没有被实际使用的现象我们通常称之为索引空洞,解决该问题我们可以通过重建表的方式来对磁盘进行回收。重建命令如下:

alert table table_name engine = InnoDB

聚集索引与辅助索引区别

我们知道InnoDB 主键使用了聚集索引。对于聚集索引,叶子节点包含了该数据的所有字段,因此当数据发生变化的时候可以直接对这一数据行进行替换。

对于辅助索引而言,情况可能有点不太一样。 当我们更新了一个辅助索引,InnoDB 首先将之前的索引标记为删除,然后插入新的索引记录,被删除的索引通过 purge 线程在后续被清除。因此如果辅助索引被其他事务更新或删除,覆盖索引将会失效,查询需要回表。