一、事物四大特性
- 原子性:事物是不可分割的,要么都完成,要么都不完成
- 一致性:事物从一种状态到下一种一致性状态,事物完成前后都不能破坏数据库的完整性约束
- 隔离性:并发下,存在多个数据对同一数据进行操作,多个事物应该互相隔离,不可见,防止数据混乱。
- 持久化:当事物提交后,对数据库的操作是永久的,事物提交后数据库崩溃,那么数据库恢复时应恢复到提交后的数据
二、并发下数据读取存在问题
- 脏读:一个事物可以读取到其他事物未提交的数据
- 不可重复读:一个事物对同一数据多次读取,读取的结果不一致,其他事物对这一数据进行了修改。
- 幻读:一个事物多次对一个范围进行查询,后面查询比前一次查询的多了几条数据。
三、事物隔离级别
- 未提交读:事物可以读取到其他事物未提交的数据,存在脏读、不可重复读、幻读等问题
- 提交读:事物只能读取到其他事物已经提交的数据,存在不可重复读、幻读的问题。
- 重复读:事物对同一数据可以重复读取,存在幻读的问题。MVVC中快照读不存在幻读,幻读存在当前读。
- 串行:one by one的读取,不存在脏读、重复读、幻读的问题
四、什么是MVCC
MVCC是多版本并发控制,一般解决读写冲突,我们采用加锁的形式,MVCC不需要加锁解决读写冲突。
为了实现MVCC,会在每一行的记录加上3个隐藏的字段:
- 行id(主键存在时,不会创建),
- 事物id
- 回滚指针
当我们的隔离级别为RR(可重复读),每开启一个事物,系统会分配一个事物id,当事物第一次调用select语句时会生成ReadView快照。ReadView有几个重要的属性:
- 事物id列表:记录当前未提交的事物id,顺序列表
- 高水位:分配给下一个事物的id
- 低水位:对应事物id列表中最小的事物的id
- 创建该列表的事物id
undo log记录各个数据版本的事物id,比较时拿到的是最新版本,通过比较事物id判断是否能访问,不能访问通过回滚指针找到前面的版本,如此进行比较:
- 当访问的事物id小于低水位时,说明该事物已经提交可以访问。
- 当访问的事物id和自身相同,说明是事物本身修改的数据,可以访问
- 当访问的事物id大于低水位小于高水位,判断事物id是存在,存在则说明事物还未提交,不能访问,不存在说明事物已经提交可以访问
- 当访问的事物id大于高水位,说明事物是在之后创建,不能访问。
更新数据时会先加锁,然后写undo log,将原来的数据行写入undo log中,创建新的数据行,记录事物id,回滚指针指向原数据行。写完undo log后会写redo log,事物id以及指针和undo的那部分相同。处理完毕后,解锁。
五、快照读和当前读
- 快照读:RR隔离级别,事物第一次读取(普通的select语句)时生成ReadView快照,之后的读取都使用该快照。RC级别的每次select都生成一个快照。RR级别下天然无幻读。
- 当前读:除了普通的select语句读取。其他的像select … for update,select … lock in share mode都是当前读,读取当前最新版本,此外insert/update/delete也会读取最新记录,RR级别下MySQL默认开启临建锁来解决幻读问题。
六、InnoDB二段提交
为什么使用二段提交?
- 写先redo log后写bin log,如果写完redo log后宕机,那么主库会把写入的数据恢复,从库并未接收到这条数据,导致主从数据不一致
- 写先bin log 后写redo log,如果写完bin log后宕机了,当么主库会回滚,从库接收的bin log日志比主库多了一条记录,导致主从数据不一致。
二段提交保证了主从数据的一致性,MySQL二段提交是在InnoDB存储引擎上(redo log属于InnoDB,bin log属于server层,每个存储引擎都拥有),当数据刷盘的时候,会先写redo log日志,为prepare(准备)阶段,同时记录事物id,写完redo log日志后,会写bin log日志同时记录事物id,写完后将redo log的状态改为commit。