MySql事务(Transaction)

目录

前言

一、事务是什么

二、事务的特性

三、事务的开启结束标志

四、事务的作用

五、事务的隔离级别

查看当前会话的隔离级别

设置隔离级别

1、读未提交(read uncommitted)

2、读已提交(read commited)

3、可重复读(repeatable read)

4、串行化(serializable)

六、事务如何合理的使用 

七、事务是如何实现的

redo log

undo log

MVCC


前言

之前介绍了mysql的锁,这次介绍一下mysql的事务。我个人觉得锁和事务是在我们工作当中用到最多的。几乎每个开发工程师都会用到。反而那些底层的binlog,redolog,buffer pool 很少在开发过程中用到。这里也会简单介绍一下这些。

一、事务是什么

事务是恢复和并发控制的基本单位,是访问并可能更新数据库中各种数据项的一个程序的执行单元。他是一个最小的不可再分的工作单元。

通常一个事务对应这一个完整的业务。比如转账业务员就是一个最小的工作单元。

例:小陈给小张转账100,小陈账户扣减100,小张账户增加100,这种就是一个最小的工作单元,要么都成功,要么都失败。

一个事务可以是一句sql,也可以是一组sql,也可以是整个程序。

二、事务的特性

1、原子性(atomicity)一个事务是一个不可分割的工作单位,事务中包括的操作要么都执行,要么都不执行。

   原子性有undo log日志来保证,undo log日志会保存每次变更前的记录,从而在发生错误的时候进行回滚。

 2、一致性(consistency)事务必须是使数据库从一个一致性状态变到另一个一致性状态。

   一致性由原子性,隔离性,持久性来保证。

 3、隔离性(isolation)一个事务的执行不能被其他事务干扰。即一个事务内部的操作即使用数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰

   隔离性由MVCC机制和Lock锁机制来保证。

 4、持久性(durability)事务一旦提交,他对数据库中的数据的改变是永久性的。

   持久性由redo log日志来保证,在每次修改数据前,数据都是记录在redo log日志中的,只有redo log日志写成功,才会往B+树里去写,如果在写B+树时断电了,可以通过redo log日志来进行恢复写入。

三、事务的开启结束标志

开启:insert, update , delete 任意一条这种DML语句就会开启事务

结束:

  1. 提交:commit; 将所有数据落库。
  2. 回滚:rollback; 将操作记录清空。

四、事务的作用

为了保证在复杂数据库操作数据的一致性,尤其是在并发访问数据的时候。

mysql事务主要用于处理操作量大,复杂度高的数据。

五、事务的隔离级别

1、读未提交(read uncommitted)

2、读已提交(read commited)

3、可重复读(repeatable read)

4、串行化(serializable)

详细说明

        初始数据:

MySql事务(Transaction)_第1张图片

查看当前会话的隔离级别

老一点的版本:select @@tx_isolation;

新版本  select @@transaction_isolation;

设置隔离级别

老一点的版本:set tx_isolation = ’read-uncommitted’;

新版本 : set transaction_isolation = ’read-uncommitted’;

MySql事务(Transaction)_第2张图片

1、读未提交(read uncommitted)

设置隔离级别:set transaction_isolation = ’read-uncommitted’;

 1)此时客户端A与客户端B都开启事务,且数据都一致。

MySql事务(Transaction)_第3张图片

MySql事务(Transaction)_第4张图片

2)客户端A事务中修改数据,但是并未提交,客户端B的事务中也能够查询到客户端A事务的数据。如果这个时候客户端A的事务回滚,这个时候客户端B查询出来的数据就是脏数据,如果有业务通过这个脏数据处理业务,就会导致后续的数据及业务的错误。

MySql事务(Transaction)_第5张图片 MySql事务(Transaction)_第6张图片

3)客户端B去修改客户端A修改的数据,会被行锁阻塞在那,如果此时客户端A事务rollback,那么客户端B通过balance = balance+50,则是通过原先的0去加50,在数据库侧不会有问题。如果是在代码中将原来的100 + 50 然后通过balance = 150,就会导致数据错误。

解决这个脏读的问题,就得将隔离级别设置为读已提交。

MySql事务(Transaction)_第7张图片

2、读已提交(read commited)

设置隔离级别:set transaction_isolation = 'read-committed';

1)此时客户端A与客户端B都开启事务,且数据都一致。

MySql事务(Transaction)_第8张图片MySql事务(Transaction)_第9张图片

2)客户端A修改了id=1 的数据,还未提交前,客户端B查询的结果是balance = 50,当客户端A提交了事务,客户端B在同一个事务里再次查询的结果与上一次不同,balance = 200了。虽然解决了脏读的问题,但也还是有不可重复读的问题。比如程序中前后两次查询同一条数据,由于其他事务更新了该数据,就会导致程序上前后数据不一致了。

MySql事务(Transaction)_第10张图片

MySql事务(Transaction)_第11张图片

3、可重复读(repeatable read)

设置隔离级别:set transaction_isolation = 'repeatable-read';

1)此时客户端A与客户端B都开启事务,且数据都一致。

MySql事务(Transaction)_第12张图片

MySql事务(Transaction)_第13张图片

 2)客户端A修改了id=1的balance为100,并且提交了事务。客户端B在客户端修改数据前后查询结果一致。即可重复读。

MySql事务(Transaction)_第14张图片 

3)此时客户端B查询的id=1的balance=200,客户端B将id=1的balance值减去50,即balance = balance - 50,执行后查询的结果并不是150,而是50,说明了数据的一致性并没有被破坏。可重复读是利用了MVCC机制实现的。后续详细介绍该机制。

4)客户端C新开一个事务,新增一条id=11,balance=300的数据且已提交,在客户端B的事务中还是未能够查询到id=11的数据,当客户端B更新id=11的数据时是可以更新成功的,此时客户端C 再次查询数据,发现id=11的数据balance还是300,由此可见解决了幻读的问题。

 MySql事务(Transaction)_第15张图片

MySql事务(Transaction)_第16张图片  

4、串行化(serializable)

设置隔离级别:set transaction_isolation = 'serializable';

1)此时客户端A与客户端B都开启事务,且数据都一致。

MySql事务(Transaction)_第17张图片 MySql事务(Transaction)_第18张图片

2)当前隔离级别是串行化,客户端B开启的事务查询了test整个表,在串行化下查询也会进行锁表。导致客户端A的事务更新失败。 

3)当客户端A查询了id=1的数据,id=1这条数据就会被加行锁,都不需要for update,则客户端B无法更新id=1的数据,但是id不为1的数据可以被正常更新。如果客户端A查询的是范围数据,那么这个查询的范围会被加上间隙锁,其他事务无法更新范围内的数据。

这种串行化的方式并发的效率极低,在开发中极少会用到。

MySql事务(Transaction)_第19张图片

MySql事务(Transaction)_第20张图片

六、事务如何合理的使用 

这个得看每个人,每个项目,每个领域模型的划分来进行合理使用了。

如果从一个程序的入口开始就开启事务,直到程序结束才提交事务。这并不是不可以,但是在程序执行的过程中可能会有各种问题出现。

1、比如并发比较高的情况下,第一个操作数据的事务会将数据锁住,那么后面的线程都会等待锁的释放,可能单机测试没啥问题,并发一起来,后续大量的线程由于等待锁超时而失败,会引发整个程序的雪崩效应。

2、比如在RR(可重复读)的隔离级别下,多个事务对同一条数据做更新,由于可重复读的性质,可能导致后续程序计算的数据不是正确的数据,从而导致后续的程序数据都是错误的。故可能会引起重大的事故。

3、比如大型的项目,设计的服务较多,设计的领域也不同,如果一个事务进行处理的话,会导致各个服务,各个领域的代码藕合在一起,会导致后期的维护和扩展都有很大的局限性。

举个简单的例子:

一个简单下单的操作,订单下单,到客户付款成功。

涉及到的领域模型分别是:交易领域,商品领域

交易领域我这边又具体划分为 订单领域和支付领域

可以使用一个事务,但是我根据不同的领域模型给拆分为了三个事务,每个事务处理自己的业务,在交给下一个业务的时候将数据提交。

1、每个域处理自己域的事,当下游出现异常情况时,不会影响到上层业务,如支付时遇到数据库波动导致的异常,导致数据回滚,不会使订单商品侧数据受到影响,下次重试时,其他域的数据不用动,只需变动支付域相关的数据即可。

2、比如支付域出现了异常,导致数据没有落库,后续排查问题的话,看到只有支付相关的数据不正确,那么直接可以缩小排查范围,只需排查支付相关程序代码即可。

3、当并发较大时,商品侧更新库存等操作时,对商品锁定的时间就会大大简短。

MySql事务(Transaction)_第21张图片

七、事务是如何实现的

先简单了解一下redo log ,undo log 和 MVCC (简单了解哈,后续再做比较详细的说明)

redo log

重做日志,是用来实现事务的持久性的。重做日志又分为2部分,重做日志缓冲(redo log buffer) 和 重做日志文件(redo log),重做日志缓冲是用在内存中,重做日志文件是在磁盘中的。

mysql每次对数据的更新都不会实时的写入到磁盘中的,而是先将数据放到缓冲池(buffer pool)中,然后再通过另一个线程将缓冲池的数据同步到磁盘中。 如果在数据同步的过程中,忽然服务器宕机了,那么缓冲池的数据则会丢失,从而导致数据丢失。因此redo log就是用来记录已经提交了的事务的修改数据,并且会将redo log日志持久化到磁盘,就算是宕机了,重启后还是会通过读取redo log来恢复数据。

为什么都是写入磁盘,反而要引入redo log呢?因为数据库存储的数据在磁盘上不是顺序的,每次找到数据需要指针寻址,比较耗时,而redo log存储的是顺序的,读取很快,只需要每次往下读取就可以了。

因此可以了解到redo log是用来确保数据的持久性的

undo log

回滚日志。用于记录数据被修改之前的数据信息。主要记录的是数据的一系列变化的过程,当有错误/异常的情况或rollback发生时,可以根据这些日志进行回滚处理。将数据回滚至修改前的状态。

undo log 是用来回滚数据的,从而保障未提交事务的原子性的

MVCC

多版本并发控制机制,是通过在每行数据增加2个隐藏字段trx_id 和 roll_pointer 记录版本号,然后与undo log日志进行串联起来形成一个历史记录版本链,在可重复读的隔离级别下,当前事务下查询sql会生成当前事务的一致性视图read-view,sql里的任何查询结果需要从undo log日志的最新数据开始与read-view做对比,从而得到最终输出的结果。

隔离性就是通过MVCC来保障的。

MVCC机制就是通过undo log日志记录版本链与read-view对比机制,使不同的事务会根据数据版本链对比规则读取同一条数据在版本链上的不同版本的数据。

到了这里,知道了事务的原子性由undo log日志保障,持久性由redo log日志保障,隔离性由MVCC机制保障。

那么事务的一致性,就是通过上面原子性,持久性,隔离性来进行保障的。

最开始已经介绍了事务的四大特性是ACID,那么刚介绍了保障了这四大特性的机制,可以得出实现事务的原理就是上面所描述的三大机制。

你可能感兴趣的:(mysql,mysql,数据库,database)