一、事务介绍
银行转账例子,你要给朋友转账100,并且卡里只有100元。在期间转账中还可以做一些列操作,比如余额查询、余额转出、余额转入等操作,如果业务中显示转账成功但是系统未处理完成,当你查询时候仍能查出这100元,那么这样的体验就非常不友好。这个时候我们可以使用事务来解决这样的问题。
事务的目的就是为了保证一组数据库操作,要么全部成功,要么全部失败。
二、ACID - 四大特性
1、原子性A
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚。
2、一致性C
一致性是指事务执行之前和事务执行之后都必须处于一致性状态,即从一个一致性状态变成另一个一致性状态。
例如:A和B进行转正操作,A目前有余额200元,B目前有200元。当A转了钱给B后,根据一致性的要求,他们的总额还是400元,不会变。
3、隔离性I
隔离性是指用户并发访问数据库时,事务不能被其他事物所做的操作干扰即多个并发事务之间应该相互隔离。
最简单的事务 - 和锁的原理一样,即一个事务完成了,另一个事务才能执行。
4、持久性D
持久性是指事务的操作,一旦提交,对于数据库的改变是永久的,即使数据库发送故障恢复后也是这个结果。
三、隔离级别
与事务最直观的特性就是隔离性,有了事务的存在,会导致多个事务执行时,会出现脏读、不可重复读、幻读的问题。
(1)脏读
脏读表示其中的一个事务读取了另一个事务未提交的数据。
(2)不可重复读
不可重复度表示一个事物多次读取数据,由于事务的提交,在一个事务内多次读取到的数据是不一致的。
(3)幻读
幻读是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。
即一个事务用Where子句来检索一个表的数据,另一个事务插入一条新的记录,并且符合Where条件,这样,第一个事务用同一个where条件来检索数据后,就会多出一条记录造成了幻读。
为了解决上面的问题我们需要使用隔离级别。SQL的标准隔离级别:
(1)读未提交
一个事务还没提交,他做的变更就能被其他事务看到。
(2)读提交
一个事务提交之后,他做的变更才可以被其他事务看到。
(3)可重复读
一个事务执行过程中看到的数据,总是和这个事物在启动时看到的数据是一致的。
(4)串行化
对同一行记录,出现读写冲突时,后访问的事务必须等前一个事务执行完成,才可以继续执行。
· 使用不同的隔离级别观察视图的返回值
(1)读未提交
虽然事务B未提交,但是根据读未提交的特性,V1仍可以读取到事务B中未提交的数据,所以V1、V2、V3都为2。
(2)读提交
根据读提交的特性,由于事务B中的数据未提交,所以V1无法读取未提交的数据,等到事务B提交后才可以读取到事务B提交后的数据,所以V1为1,V2、V3为2。
(3)可重复读
根据可重复读的特性,事务A执行过程中读取的数据都是保持一致的,因此V1、V2都为1,V3属于事务A提交和事务B提交之后的所以V3为2。
(4)串行化
串行化类似读写锁,事务A先获得读锁,事务B读写冲突,需要等事务A释放锁,因此V1、V2为1,V3读取数据时锁已释放所以V3为2。
· 如何查看事务
show variables like 'transaction_isolation';使用该语句即可查看事务。运行结果如下图:
我这里采用的是默认的隔离级别,读提交。
隔离级别就相当于资源的抢占方式,隔离级别越高,表示隔离的越严格,效率就会越低。因此我们需要在隔离级别和效率中根据业务来评估选择合适的隔离级别。
举一个比较经典的隔离级别为可重复读的例子:
假设使用数据库在进行数据对账时,那么在对账的时候需要用到的是某一时刻的账户对账值,不希望他被其他事物操作而修改值,即 - 即使有用户发生了一条新的交易,也不影响对账的结果。根据可重复的特性,这里使用可重复读作为隔离级别就非常合适。
四、事务隔离的实现
在mysql中,每条更新操作的同时都会记录一条回滚操作来方便我们rollback。
假设一个值从1被按顺序依次+1,被改为2、3、4,回滚日志如下:
如上图所示,不同的事务会有不同的视图,并且在不同的视图中的值记录的是操作前的值即1、2、4。
如果回滚得到1,则需要依次回滚。如果有其他事务D要将4修改为5,和视图ABC也不会冲突,可以正常执行。
最后这些回滚日志会在系统判断为不需要的时候将他们删除。
五、事务的启动方式
mysql的事务启动方式:
(1)显示启动事务语句 - begin 或 start transaction。提交语句 - commit。commit work and chain - 提交语句并自动启动下一个事务。回滚语句 - rollback。
(2)set autocommit = 0,这个命令会将这个线程的自动提交关掉。意味着如果你只执行一个select语句,事务就启动了,并且不会自动提交。直到你主动执行commit或rollback,或者断开连接。
如果使用set autocommit = 0,需要注意避免查询导致的长连接,导致意外的长事务。
六、总结
结合上述内容总结了几个问题,如下:
(1)事务的概念
指事务包含的所有操作,要么全部成功,要么全部失败回滚。
(2)mysql的事务隔离级别
读提交、读未提交、可重复读、串行性。
(3)可重复读的使用场景
不希望被其他事务干扰的对账功能。
(4)并发版本控制(MVCC)的概念,是怎么实现的?
同一条记录在数据库中会有不同的版本,实现方式通过read-view视图来实现,当每个事务启动会启动一个视图,每个视图都有一个版本id来区分。
(5)为什么使用长事务可能会拖垮整个库?
因为长事务在连接的这段时间,由于无法删除read-view日志,需要用该日志防备回滚,如果长事务越来越多了会导致占用的内存越来越大,从而拖垮整个库。
(6)事务的启动方式
有2种启动方式,见第五段。
(7)commit work and chain的语法的作用
提交事务并启动下一个事务。
(8)如何避免长事务的出现?
监控innodb_trx中长连接的长度,如果过长就报警。
(9)怎么查询各个表中的长事务?
select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>10;
可以查询超过10s的事务。