事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
读未提交(read-uncommitted) | 可能 | 可能 | 可能 |
读已提交(read-committed) | 不可能 | 可能 | 可能 |
可重复读(repeatable-read) | 不可能 | 不可能 | 可能 |
串行化(serializable) | 不可能 | 不可能 | 不可能 |
1、脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
2、不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致。(修改、删除)
3、幻读:事务 A 多次读取同一数据,事务B插入一条数据,事务B读到新插入的数据,结果不一致,这就叫幻读。(插入)
解决可重复读:mvcc机制,主要由两个隐式字段(DB_TRX_ID、DB_ROLL_PTR
),readview,undo日志实现。
隐式字段:
DB_TRX_ID:记录最后一次修改该记录的事务id。
当每个事务执行修改记录时,都会被分配一个ID, 这个ID是递增的DB_ROLL_PTR:回滚指针,指向当前记录的最新历史版本。
Read View:就是事务
首次
快照读
操作的时候产生的读视图
,主要由当前未提交事务ID数组、未提交事务ID数组最小值(up_limit_id
)、所有事务ID的最大值+1(low_limit_id
)三部分组成。用于可见性判断,查询的结果集根据readview做比对生成快照。
比对规则:
- 首先比较DB_TRX_ID < up_limit_id, 如果小于,则当前事务能看到DB_TRX_ID 所在的记录,如果大于等于进入下一个判断
- 接下来判断 DB_TRX_ID 大于等于 low_limit_id , 如果大于等于则代表DB_TRX_ID 所在的记录在Read View生成后才出现的,那对当前事务肯定不可见,如果小于则进入下一个判断
- 判断DB_TRX_ID 是否在活跃事务之中,trx_list.contains(DB_TRX_ID),如果在,则代表我Read View生成时刻,你这个事务还在活跃,还没有Commit,你修改的数据,我当前事务不可见,当前自己事务的修改可见;如果不在,则说明,你这个事务在Read View生成之前就已经Commit了,可见
- 删除是修改的特殊情况,也会加入undo日志,修改DB_TRX_ID,同时在该记录的头信息(record header)里(deleted flag)标记位写上true,查询遵循以上规则,并将忽略标记位为true的记录。
undo日志:一条历史记录链,链首就是最新的旧记录,链尾就是最早的旧记录。
补:RC级别每次快照读都生成readview,RR级别首次执行快照读时生成,并之后一直沿用。
快照遵循规则:当前事务内的更新,可以读到;版本未提交,不能读到;版本已提交,但是却在快照创建后提交的,不能读到;版本已提交,且是在快照创建前提交的,可以读到。
聚集索引:主键索引
二级索引:非主键索引的索引
解决幻读:临键锁(!=行锁+间隙锁)
当检索条件为聚集索引时并且不在间隙锁(左开右开)范围内,会在索引树上定位行,只加上行锁,否则,只加间隙锁。
当检索条件为二级索引时并且不在间隙锁范围内,会加上行锁,并在二级索引左边和右边都加上临键锁(左闭右开),否则,只加临键锁。
当检索条件不为索引时,数据库会为整个表加上间隙锁和行锁。
加上行锁后,MySQL 会进行一遍过滤,发现不满足的行就释放锁,最终只留下符合条件的行。虽然最终只为符合条件的行加了锁,但是这一锁一释放的过程对性能也是影响极大的。
注:临键锁间隙锁是在对应区间内不能插入数据,行锁是不能修改对应行数据。
参考链接:https://blog.csdn.net/SnailMann/article/details/94724197
https://baijiahao.baidu.com/s?id=1662096005584873447&wfr=spider&for=pc