Mysql部分到此结束
正文什么是事务
- 是数据库最小的工作单元,不可再拆分。
- 可能包含一个或者一系列的DML语句,包括insert,update,delete。(当然DDL,DCL也会包含事务)
事务特性
- 原子性:一次多个操作要么都成功,要么都失败。通过undo.log实现回滚,保证原子性。
- 隔离性:多个事务之间互不影响。通过MVCC + LBCC实现。
- 持久性:事务提交成功,结果应当写入磁盘。通过duuble write pool(双写缓冲)+redo.log实现。
- 一致性:事务开启前结束后,数据都是合法的状态。通过原子性+隔离性+持久性前三种特性来保证这一特性。
前提知识
- 事务都是有编号的,且会不断递增
- InnoDB为每行数据都设置了两个隐藏字段
- DB_TRX_ID:事务ID,此数据是在哪个事务插入或者修改为新数据的,就记录为当前事务id。
- DB_ROLL_PTR:回滚指针,删除版本号,正常为null,当数据被删除或者记录为旧数据(即被修改)时,记录当前事务ID。
MVCC (多版本并发控制)
只能查找创建时间小于等于当前事务ID的数据,和删除时间大于当前事务ID的行(或者未删除)。
undo.log链
当一个数据被标记为旧版本(即被修改)时,是被存在undo.log中的。
同一条数据被修改了多次,这些同一条数据的旧版本就会形成了一个链条,叫做undo.log链。
前面的DB_ROLL_PTR就是指向undo.log的指针。
具体是如何存储的这里就不详细说了,有点复杂。
简单来说,就是可以通过这个字段拿到属于自己可以拿到的旧版本,生成快照。 =.=
LBCC
锁的粒度
- 行锁
- 表锁
锁的类型
- 共享锁:只能读,不能写
- 排他锁:不能读写
- 意向锁:加锁前申请意向锁,提高加锁效率。
行锁原理
通过索引进行加锁,未走索引会造成表锁。
行锁的算法
以下内容都是在默认RR隔离级别下
Record Lock(记录锁)
主键值即record,如上图:数据库里有两条数据key为10和20,这就是对应两个记录锁
对唯一索引/主键索引进行等值加锁查询即产生记录锁(对于普通索引加锁范围不了解)
Gap Lock(间隙锁)
record之间不存在的区间(左开右开),即为间隙锁。
当我们的查询未命中一个record,无论是等值还是范围查询都会产生这个范围内的间隙锁。
Next-Key Lock(临建锁)
record lock+ 其左边的gap lock即为临建锁,范围左开右边闭。
当我们使用范围查询命中了一条数据时,会锁住下个key的左开右闭区间。
例:如上图所示内容:select * from table where Key >8 and Key<15;
结果:会产生Gap Lock1+Record Lock1+Gap Lock2+Record Lock2 即(0,10]+(10,20]
原因:主要为了解决幻读的问题,这也是为啥innoDB在RR级别下就解决了幻读。
事务总结
Read-Uncommited
RU不加锁
Serializable
所有select会被隐氏的转换成select ... in share mode ,会和update、delete互斥。
Repeatable-Read
默认隔离级别 普通select使用快照读,底层使用MVCC实现。
加锁的select,update,delete等语句使用当前读,底层使用记录锁,或者间隙锁,临建锁,。
Read-Commited
普通select使用快照读,底层使用MVCC实现。
加锁的select,update,delete等语句使用当前读,底层使用记录锁,其没有间隙锁。
避免死锁
- 操作多张表时,按照固定的顺序进行访问(避免环路等待)。
- 批量操作单表多个数据,先进行排序,即按照一定顺序访问(避免环路等待)。
- 申请足够级别的锁,如果要操作数据,就申请排他锁。
- 尽量使用索引去访问数据,避免锁表。
- 大事务转化成小事务(这里是大化小,还是小化大看情况而定)。
- 使用等职查询而不是范围查询,避免间隙锁的影响。