事务的四个特性,ACID。来聊一聊一致性。
并发与一致性的关系
并发的任务对同一个临界资源进行操作,如果不采取措施,可能导致不一致,故必须进行并发控制
常用手段
通过并发控制保证数据一致性的常见手段有:
- 锁(Locking)
- 数据多版本(Multi Versioning)
锁
锁的演化思路
- 串行的排它锁,并发性能不高
- 锁的读写分离,X锁和S锁,即排它锁和共享锁,提高并发性能。但这里读还是会受到写锁的影响,并没有真正的将读和写分离,而是实现的读和读的并发。
共享锁与排他锁的玩法
- 共享锁之间不互斥,所以可认为读读可以并行
- 排他锁与任何锁互斥,所以可认为写读,写写不可以并行
可以看到,一旦写数据的任务没有完成,数据是不能被其他任务读取的,这对并发度有较大的影响。对应到数据库,可以理解为,写事务没有提交,读相关数据的select也会被阻塞。
此时,提高性能的关键就是,如何让读和写可以并行,以此来提高并发性能。
多版本并发控制
可以想到的一个操作是,当一个事务进行写操作的时候,把要操作的数据存一份,作为一个版本,其他事务进行读操作的时候,读取的数据是原有数据的版本,这样读操作并没有没已存在的写操作阻塞住,从而提高了并发度。
这样带来的一个结果就是,一个读事务,读到的要么是其他写事务提交之后的数据,要么是其他写事务未提交时修改前的数据。这样在提高并发性能的同时也避免了脏读。
那么多版本并发控制是怎么做呢?
数据库事务未提交时,会将事务修改数据的镜像(即修改前的旧版本)存放到undo日志里,当事务回滚时,或者数据库奔溃时,可以利用undo日志,即旧版本数据,撤销未提交事务对数据库产生的影响。
undo日志存在回滚段里,多版本并发控制就是通过读取回滚段的旧版本数据来实现的。
回滚段里的数据,其实是历史数据的快照(snapshot),这些数据是不会被修改,select可以肆无忌惮的并发读取他们。
快照读(Snapshot Read),这种一致性不加锁的读(Consistent Nonlocking Read),就是InnoDB并发如此之高的核心原因之一。
这里的一致性是指,事务读取到的数据,要么是事务开始前就已经存在的数据(当然,是其他已提交事务产生的),要么是事务自身插入或者修改的数据。
总结
- 常见并发控制保证数据一致性的方法有锁,数据多版本;
- 普通锁串行,读写锁读读并行,数据多版本读写并行;
- redo日志保证已提交事务的ACID特性,设计思路是,通过顺序写替代随机写,提高并发;
- undo日志用来回滚未提交的事务,它存储在回滚段里;
- InnoDB是基于MVCC的存储引擎,它利用了存储在回滚段里的undo日志,即数据的旧版本,提高并发;
- InnoDB之所以并发高,快照读不加锁;
- InnoDB所有普通select都是快照读;
参考文章: