谈谈mysql中的锁

前言

今天又来写博客了,没错今天又被面试官锤了,这次被锤的问题是关于mysql中关于锁的问题,特此整理下有关锁的问题,避免今后对这种问题再次犯同样的错误(꒦_꒦)

什么是锁?为什么要加锁

1
2
3
4
5
锁是计算机协调多个进程或纯线程并发访问某一资源的机制。在数据库中,除传统的计算资源(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锁的一种,这种锁会在记录插入前设置。这种锁表示了多个事务在插入到相同的索引间隙的时候,只要他们不是插入到相同的位置上就不必彼此互相等待。在获取插入的排它锁之前,需要先获取插入意向锁

insert-intention-locks

谈到这里其实可以聊聊今天被锤的一个面试问题,有关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