前言
今天又来写博客了,没错今天又被面试官锤了,这次被锤的问题是关于mysql中关于锁的问题,特此整理下有关锁的问题,避免今后对这种问题再次犯同样的错误(꒦_꒦)
什么是锁?为什么要加锁
1 | 锁是计算机协调多个进程或纯线程并发访问某一资源的机制。在数据库中,除传统的计算资源(CPU,RAM,I/O) |
锁分类
图片引用自参考文章MySQL InnoDB锁机制全面解析分享
按照加锁机制进行分类
- 乐观锁 乐观锁乐观地假定大概率不会发生更新冲突,访问、处理数据的时候不加锁,只在更新数据的时候查看版本号是否有冲突,有则处理、无则提交事务
- 悲观锁 悲观锁悲观地假定大概率会发生更新冲突,访问或者处理数据前加排他锁,在整个数据处理过程中锁定数据,事务提交或者回滚后才释放锁
按照兼容性进行分类
- 共享锁 也就是读锁,读操作之间互相不冲突,但是读操作跟写操作互相冲突,也就是会阻塞写请求
- 排他锁 也就是写锁, 写操作之间互相冲突,写操作跟读操作互相冲突
按照锁的粒度进行分类
- 表级锁 mysql中锁定粒度最大的一种锁,对整张表进行加锁,实现简单,资源消耗较少,加锁快,不会出现死锁。由于锁定粒度最大,触发锁冲突的概率最高,并发度最低,myisam和innodb引擎都支持表级锁
- 页级锁 开销和加锁时间介于表锁和行锁之间,会出现死锁,并发程度一般
- 行级锁 开销大,加锁慢,会出现死锁,锁定粒度最小,发生锁冲突的概率最低,并发程度也最高
按照锁模式进行分类
- record lock 记录锁,对符合条件的行进行加锁,锁住索引项
- gap lock 间隙锁,对符合条件的记录行之间的间隙进行加锁,不包含符合条件的索引项本身,只是锁定记录的范围,其他事务不能在锁范围内插入数据,这样就防止了别的数据新增幻影行(幻读)
- next-key lock 锁定索引项本身和索引范围,即record lock + gap lock的结合 可解决幻读问题
- 意向锁 为了允许表锁和行锁共存,实现多粒度锁机制,innodb还有两种内部使用的意向锁(表级锁)分为
- 意向共享锁(IS) 事务打算给数据行加共享锁,必须先取得该表的IS锁
- 意向排他锁(IX) 事务打算给数据行加排他锁,必须先取得该表的IX锁
意向锁是在给某一行进行加锁的时候,mysql会自动为这一行所处的表进行加意向锁,无需用户任何处理,意向锁的作用我们可以看下面一个例子
时间 | 事务1 | 事务2 |
---|---|---|
t1 | 更新table a中某一行数据 | do nothing |
t2 | do nothing | 更新table a全表数据,需要获取表锁 |
t3 | 提交 | — |
t4 | — | 提交 |
在上述例子中,事务1中在t1时刻对表中数据进行数据更新时,自动加该表的意向排他锁,这时事务2获取该表的写锁时会先判断是否和意向锁兼容,发现该表存在意向排他锁,锁类型不兼容,需要等待事务1提交后才能进行操作。
这样如果某张表存在行级锁,获取表锁的时候就不必遍历整张表判断是否存在行级锁,直接判断是否与表级的意向锁是否兼容就可以了,下面给出表级的意向锁和读写锁的兼容情况
—- | IS | IX | S | X |
---|---|---|---|---|
IS | 兼容 | 兼容 | 兼容 | 冲突 |
IX | 兼容 | 兼容 | 冲突 | 冲突 |
S | 兼容 | 冲突 | 兼容 | 冲突 |
X | 冲突 | 冲突 | 冲突 | 冲突 |
- 插入意向锁 插入意向锁是gap锁的一种,这种锁会在记录插入前设置。这种锁表示了多个事务在插入到相同的索引间隙的时候,只要他们不是插入到相同的位置上就不必彼此互相等待。在获取插入的排它锁之前,需要先获取插入意向锁
谈到这里其实可以聊聊今天被锤的一个面试问题,有关SQL优化相关的问题
面试中被面试官锤的问题
表级锁了解吗?什么时候会触发表级锁?update语句什么情况下会触发表级锁?
现在mysql默认存储引擎都是innodb,那么innodb存储引擎下什么情况会进行锁表呢?这就要从mysql的索引讲起了 innodb中默认使用行级锁,这个行级锁锁的是索引记录,换句话说,如果你的SQL语句中如果没有索引,这时innodb会锁住谁呢?毫无疑问这个时候没有索引就会触发了表级锁,实际使用中我们要特别注意innodb行级锁的这个特性,不然会引起大量的锁冲突,从而影响并发性能, update语句中什么情况会触发表级锁可以看下面哪些情况会锁表
都有哪些情况innoDB会锁表呢?
其实不管是读操作(select)还是写操作(update,delete,insert),只要涉及到带有筛选条件的语句,如果筛选条件中没有用到索引,就会触发全表扫描,区别是读操作可能加读锁(也有可能不加锁,mvcc中的快照读是通过版本号实现的,不加读锁,当前读需要加读锁),写操作默认需要对影响的数据集隐式加写锁,那么如果发现影响的数据集没有用到索引或者是索引效果不好(区分度不够高,导致需要扫描表中大部分数据)再或者全表扫描的时候,就会锁住整张表,导致默认的行级锁升级为表级锁,因此我们总结下以下情况会导致锁表
- 全表更新 事务需要更新大部分数据或全部数据,如果使用行级锁,会导致事务执行效率低,从而导致其他事务长时间等待锁和更多的锁冲突
- 多表级联 事务涉及多张表,比较复杂的关联查询,很可能造成死锁,这种情况若能一次性锁住事务涉及的表,从而避免死锁,减少数据库事务回滚所带来的开销
- 本应部分更新,但是因为筛选条件中未用到索引或者索引区分度程度不高(innodb认为全表扫描比走索引效率更高导致索引失效的情况),导致全表扫描,这个时候就要通过explain去查看下查询计划,看下查询语句是否真的用到了索引
锁优化部分
- 尽量让数据检索都通过索引来完成,避免无索引或者索引失效导致行级锁升级为表级锁
- 合理设计索引,以缩小加锁范围,避免间隙锁造成不该锁定的键值被锁定
- 尽量控制事务的大小,因为行级锁的复杂性会加大资源使用量以及锁定时间
这里面实际上第三种情况是可以避免的,在做业务时,需要谨慎的加索引,在合适的列上创建索引,索引列区分程度是否高(主键索引和唯一索引不用说区分度百分百,如果能用到主键索引或者唯一索引就尽可能的使用这两种索引,如果不能使用,确保索引列区分程度够高)
参考文章
MySQL InnoDB锁机制全面解析分享
MySQL锁总结
mysql官方文档#insert-intention-locks