文章内容参考https://mp.weixin.qq.com/s/v0e3xqzR7ftFXurG_U8XYA
在此基础上的个人整理
mysql中,事务其实就是最小的不可分割的工作单元,事务能够保证一个业务的完整性。
事务:一个最小的不可再分的工作单元
通常一个事务对应一个完整的业务(例如银行账户转账业务,该业务就是一个最小的工作单元)一个完整的业务需要批量的DML(insert、update、delete)语句共同联合完成事务只和DML语句有关,或者说DML语句才有事务。这个和业务逻辑有关,业务逻辑不同,DML语句的个数不同
原子性即为不可分割的最小单元,保证多条DML语句要么同是成功,要么同是失败,底层使用undo log来保证,undo log记录着事务发生前和事务发生后的多份数据
多个事务并发时,互不干扰,如果多个事务同时操作相同的数据,那么就对产生对应的脏读,不可重复度,幻读的问题。所以innodb提供了4种事物隔离级别,如下:
不同隔离级别的本质实际上都是通过锁来解决的,innodb为了保证事务的一致性,只要写数据,都会加入排他锁
读不加锁,那么其他事务在对数据进行操作但又未提交时,即便其他事务对数据加入了排他锁,但是读取数据时没有加锁,自然的就不会形成隔离效果,当前事务就会读取到其他事务修改的内容,即产生了脏读
试想:如果上述情况读数据时加了锁,那么读请求就会阻塞,则查询性能变得很差
为了提升并发性能,innodb使用了多版本并发控制(Multi-Version Concurrency Control,MVCC)来解决。MVCC通过生成数据快照(Snapshot),并用这个快照来提供一定级别(语句级或事务级)的一致性读取
MVCC使用undo log + ReadView实现

undo log中事务的修改都会生成 一条新数据,新数据中记录的修改内容,以及额外记录由哪个事务做的修改和修改前上一条数据的指向
在读已提交中,使用语句级快照来实现,即每一次查询时,都会生成快照ReadView,ReadView中保存了正在进行中的事务的id,那么在查询数据时,通过判断undo log当前读取到的数据的事务id是否在ReadView中,如果不在则查询成功,如果在就查询roll_pointer中保存的上一次的数据引用,通过这种方式来做到避免读取尚未提交的事务中的数据
读已提交造成的问题就是不可重复读。即当前事务先读取到了数据A,另外一个事务将A改成B,并提交事务,此时当前事务再次读取,则读取到的内容为B,导致一个事务对相同的数据查询两次结果不一致,这就是不可重复读
可重复度的隔离级别解决了不可重复读的问题,本质上是因为可重复度的ReadView快照在当前事务开启的时候生成(事务),再当前事务没提交之前都不会再次生成快照,进而解决了不可重复读。
而不可重复读会导致幻读问题,幻读指的是当前事务的读会受到其他事务已经提交成功的新增或删除行为的影响
| 时间 | 事务1 | 事务2 |
|---|---|---|
| T1 | begin; | |
| T2 | select * from test; | |
| T3 | insert into test value(3,33); | |
| T4 | select * from test; | |
| T5 | update test set name=‘333’ where id = 3; | |
| T6 | select * from test; | |
| T7 | commit; |
不允许事务并发,全都按照顺序执行,性能最差
持久性指的是事务只要提交完成,那么对于磁盘来说就是永久性的修改
在mysql的处理逻辑中,数据并不是没发生修改就立即持久化到磁盘中,为了提高性能,修改会立马在内存中生效,然后按照一定的刷盘策略同步到磁盘上,那么内存中修改完成后但未持久化到磁盘的过程中mysql发生宕机,在mysql重启后依旧能够保持数据持久性,就是因为在内存中更新的同时,会将具体的修改内容保存到redo log中
直接在磁盘修改真实数据,属于随机写,需要找到写入的地方,速度偏慢,redo log中记录的是将某page做了某修改,并且是顺序写,写入速度很快,并且文件很小,恢复也很快
一致性即使用事务的目的,原子性、隔离型、持久性都是为了保证一致性的手段,一致性需要由程序来保证
比如当出现了异常情况,那么程序需要保证事务的回滚,无异常时,需要手动提交事务