概念
事务是一系列操作所构成的执行单元,具有原子性,隔离性等的特点。
锁是为了防止多线程读写操作的并发问题而引入的解决方案。
- 锁
- 锁主要分为乐观锁和悲观锁,乐观锁认为不需要加锁,当多线程共同写操作时,引入version版本号机制。当读取的版本号与写入时的版本号不一致,则认为该数据已经被修改,写操作失败。悲观锁认为任何时候都需要加锁,所以多线程操作时,只有一个线程释放锁后,其他线程才能执行获取锁或者访问(需要区别读锁和写锁)。数据库的锁基本都是悲观锁。当然我们也可以使用乐观锁解决并发问题https://www.cnblogs.com/laoyeye/p/8097684.html
- 锁的分类
- 读锁和写锁
- 读锁也叫共享锁(s锁),当一个事务(或者线程)对某一个对象加读锁时,其他事务可以再加读锁,但是不能加写锁
select ... lock in share mode;
- 写锁也叫排他锁(x锁),当一个事务(或者线程)对某一个对象加写锁时,其他事务不能再加读锁或者写锁。
select ... for update 或者update,delete等
note:select不加任何锁,所以select查询任何时候都不会被阻塞。- 按照加锁的范围分为行锁和表锁
行锁和表锁是指加锁的范围,读写锁是指加锁后其他事务能够加锁或者访问。
- 表锁:锁住整张表,两个事务不能同时对一张表做更新操作,这样会降低并发性,但是不会出现死锁,也是大多数数据库引擎支持的类型。
- 行锁是表中的一行或者多行加锁,这是Innodb的一个特征,行锁是根据索引加锁,所以如果查询没有使用索引,则会退化为表锁。
- 如果使用了相同的索引值,即使是不同行也会冲突,造成不能加锁或访问。
- 事务使用表中不同的索引时不会发生冲突。
- 行锁的分类(记录锁,间隙锁,临间锁(记录锁+间隙锁))
- 记录锁:锁住某几行记录
- 间隙锁:为了解决可重复读中的幻读问题,所以只有可重复读和串行化中存在间隙锁。
- 事务
- 事务的种类:读未提交,读已提交,可重复读,串行化
- 读未提交,一个事务读取了另一个事务未提交的更新,会导致出现脏读。事务a进行更新时,事务b使用select(任何时候都可以读)查询到了修改的记录,但事务a并未提交,导致事务b读取到了脏数据。
- 读已提交:读取一个事务已提交的更新,可以解决脏读。使用mvcc机制实现:
mvcc(多版本并发控制):数据库会生成ReadView(当前活跃的事务id)和版本链(版本链中的每个记录都会有一个事务id(trx_id)和指向上一次修改的指针roll_pointer),事务读取记录时,如果发现事务a的id小于要查询记录的版本链中中的事务id,说明这个版本记录中的事务已经提交了,可以使用;如果发现查询记录中版本链中的事务id在ReadView中,则说明该事务还未提交,则不能使用,然后查找roll_pointer指向的下一条记录,然后比较事务id是否在ReadView中。
note:读已提交和可重复读都使用了mvcc但是使用读已提交时,每次select都会生成新的ReadView,所以第二次select可以查到与第一次不同的数据。但是可重复读为了不解决一个食物中不可重复读的问题,不生成新的ReadView,这样一个事务中两次select的是同样的数据。
mvcc中记录的版本链示意图
- 可重复读(innodb的默认隔离级别):可重复读解决了在同一个事务中多次读取数据不一致的情况。实现方式分为select实现和非select实现。
- select实现(快照读):select不加锁,为了让select实现可重复读,RR将RC(读已提交的)的mvcc拿过来,但是同一事务中每次select都使用同一个ReadView所以,读取的是历史数据,所以此时select也被叫做快照读。
- 非select实现(当前读):RR中除了select之外的都会更新ReadView,所以也叫当前读。但是会出现不可重复读和幻读的问题(详见下),所以非select方式(当前读)使用临间锁(记录锁+间隙锁)(详见下)实现。即通过锁不让其他事务在指定范围内更新数据。既保证了数据新鲜又能可重复读。
- 不可重复读:在当前读时如果只使用记录锁,则再次查询时会出现新插入的记录。例如:数据表中有三条记录(id=1,2,3),事务a查询id小于10的记录,总共三条,但是锁住的只是id为123的记录,事务b插入新的记录不阻塞,则事务a再次查询时会出现四条记录。
- 幻读:数据表中没有id为2的记录,事务a查询后insert一条id为2的记录,但是事务b也insert一条id为2的记录,这时由于查询时没有锁住id为2的行,所以事务a会插入失败,提示id冲突,即见鬼了。
- 间隙锁:对索引之间的范围加锁,使得其他事务不能在该范围内更新
- 当索引是主键索引和唯一索引时精确匹配时,只锁住该行(=或者in),当查询值不存在时会退化为间隙锁
- 当索引是普通索引时,通过精确匹配会锁住满足检索条件的范围(=后者in)例如,索引是2,5,11,23,45,78,当查询in(5, 45)时会锁住[2, 5)和[5, 11),[23, 45)和[45, 78)当插入[11, 23)时不会阻塞。如果查询值为10,则锁住[5, 11)
- 范围查找时会锁住指定范围,例如id>10 and id<30将锁住,如果10,30不是索引,索引值为4, 15, 17, 27, 35, 56将锁住[4, 35)
- 串行化:会将所有的select语句加上共享锁lock in share mode,这将导致读写不能并发,效率低。
参照:MySQL的锁机制和加锁原理
mvcc机制
Innodb锁机制:Next-Key Lock 浅谈
面试中的老大难-mysql事务和锁,一次性讲清楚! - 掘金 (juejin.cn)
网友评论