MySQL事务

42 阅读6分钟

事务的特性如何实现

事努有4种特性:原子性、一致性、隔离性和持久性。那么事务的四种特性到底是基于什么机制实现呢?

  • 事务的隔离性由锁机制实现。
  • 事务的原子性、一致性和持久性由事务的redo日志和undo日志来保证。
    • redo log称为重做日志 ,提供再写入操作,恢复提交事务修改的页操作,用来保证事务的持久性。
    • undo log称为回滚日志,回滚行记录到某个特定版本,用来保证事务的原子性、一致性。

有的DBA或许会认为undo是redo的逆过程,其实不然。redo和undo都可以视为是一种恢复操作,但是:

  • redo log:是存储引擎层 (innodb)生成的日志,记录的是"物理级别"上的页修改操作,比如页号xxx、偏移量yyy写入了'zzz'数据。主要为了保证数据的可靠性;
  • undo log:是存储引擎层 (innodb)生成的日志,记录的是逻辑操作日志,比如对某一行数据进行了insert语句操作,那么 undo log就记录一条与之相反的DELETE操作。主要用于事务的回滚(undo log记录的是每个修改操作的逆操作)和一致性非锁定读(undo log回滚行记录到某种特定的版本--MVCC,即多版本并发控制)。

事务的特性

  • 原子性(Atomicity)
 原子性是指事务是一个不可分割的工作单位,要么全部提交,要么全部失败回滚。即要么转账成功,要么转账失败,是不存在中间的状态。
 如果无法保证原子性会怎么样?就会出现数据不一致的情形,A账户减去100元,而B账户增加100元操作失败,系统将无故丟失100元。
  • 一致性(Consistency)
 根据定义,一致性是指事务执行前后,数据从一个合法性状态变换到另外一个合法性状态。这种状态是语义上的而不是语法上的,跟具体的业务有关。
 那什么是合法的数据状态呢?满足预定的约束的状态就叫做合法的状态。通俗一点,这状态是由你自己来定义的(比如满足现实世界中的约束)。满足这个状态,数据就是一致的,不满足这个状态,数据就是不一致的!如果事务中的某个操作失败了,系统就会自动撤销当前正在执行的事务,返回到事务操作之前的状态。
  • 隔离性(Isolation)
 一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能相互干扰。
  • 持久性(Durability)
 持久性是指—个事务一旦被提交,它对数据库中数据的改变就是永亼性的,接下来的其他操作和数据库故障不应该对其有任何影响。
 持久性是通过事务日志来保证的。日志包括了重做日志和回滚日志。当我们通过事务对数据进行修改的时候,首先会将数据库的变化信息记录到重做日志中,然后再对数据库中对应的行进行修改。这样做的好处是,即使数据库系统崩溃,数据库重启后也能找到没有更新到据库系统中的重做日志,重新执行,从而使事务具有持久性。

数据并发问题

脏写

对于两个事务 Session A、 Session B,如果事务 Session A修改了另一个未提交事务 Session B修改过的数据,那就意味着发生了脏写。示意图如下:

数据并发问题-脏写示意图.png

脏读

对于两个事务 Session A、 Session B, Session A读取了已经被 Session b更新但还没有被提交的字段。之后若Session B回滚,Session A读取的内容就是临时且无效的。

数据并发问题-脏读示意图.png

不可重复读

对于两个事务 Session A、 Session B, Session A读取了一个字段,然后 Session B更新了该字段。之后 Session A再次读取同一个字段,值就不同了。那就意味着发生了不可重复读。

数据并发问题-不可重复读示意图.png

幻读

对于两个事务 Session A、 Session B, Session A从一个表中读取了一个字段,然后 Session B在该表中插入了一些新的行。之后如果 Session A再次读取同一个表,就会多出几行。那就意味着发生了幻读

数据并发问题-幻读示意图.png

4种隔离级别

 -- 解决脏写
 READ UNCOMMITTED:读未提交,在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。不能避免脏读、不可重复读、幻读。
 ​
 -- 解决脏写,脏读
 READ COMMITTED:读已提交,它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。可以避免脏读,但不可重复读、幻读问题仍然存在。
 ​
 -- 解决脏写,脏读,不可重复读
 REPEATABLE READ:可重复读,事务A在读到一条数据之后,此时事务B对该数据进行了修改并提交,那么事务A再读该数据,读到的还是原来的内容。可以避兔脏读、不可重复读,但幻读问题仍然存在。这是MySQ的默认隔离级别。
 ​
 -- 解决脏写,脏读,不可重复读,幻读
 SERIALIZABLE:可串行化,确保事务可以从一个表中读取相同的行。在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作。所有的并发问题都可以避免,但性能十分低下。能避免脏读、不可重复读和幻读。

MVCC

什么是MVCC

 MVCC(MultiVersion Concurrency Control),多版本并发控制。顾名思义,MVCC是通过数据行的多个版本管理来实现数据库的并发控制。这项技术使得在InnoDB的事务隔离级别下执行一致性读操作有了保证。换言之,就是为了查询一些正在被另一个事务更新的行,并且可以看到它们被更新之前的值,这样在做查询的时候就不用等待另一个事务释放锁。
 通过间隙锁next-key locking策略防止幻读的出现。

ReadView数据结构

ReadView是一个数据结构,包含4个字段

  • m_ids:当前活跃的事务编号集合
  • min_trx_id:最小活跃事务编号
  • max_trx_id:预分配事务编号,当前最大事务编号+1
  • creator_trx_id:ReadView创建者的事务编号

RC隔离级别下的MVCC

  • 读已提交:在每一次执行快照读时生成ReadView

MVCC-RC隔离级别-执行流程.png

MVCC-RC隔离级别-第一个快照读.png

事务D第1个select查询,
版本链最近的TRX_ID = 3代入访问规则
1 trx_id(3) = trx_id(4):不成立,不能访问
2 trx_id(3) < min_trx_id(2):不成立,不能访问
3 trx_id(3) > min_trx_id(5):不成立,不能访问

版本链最近的TRX_ID = 2代入访问规则
1 trx_id(2) = trx_id(4):不成立,不能访问
2 trx_id(2) < min_trx_id(2):不成立,不能访问
3 trx_id(2) > min_trx_id(5):不成立,不能访问

版本链最近的TRX_ID = 1代入访问规则
1 trx_id(1) = trx_id(4):不成立,不能访问
2 trx_id(1) < min_trx_id(2):成立,可以访问,张三

MVCC-RC隔离级别-第二个快照读.png

事务D第2个select查询,
版本链最近的TRX_ID = 3代入访问规则
1 trx_id(3) = trx_id(4):不成立,不能访问
2 trx_id(3) < min_trx_id(3):不成立,不能访问
3 trx_id(3) > min_trx_id(5):不成立,不能访问

版本链最近的TRX_ID = 2代入访问规则
1 trx_id(2) = trx_id(4):不成立,不能访问
2 trx_id(2) < min_trx_id(3):成立,可以访问,张小三

RR隔离级别下的MVCC

  • 可重复读:仅在第一次执行快照读时生成ReadView,后续快照读复用

MVCC-RR隔离级别-执行流程.png