prat1:mysql的锁你都不知道?不会吧 (什么是锁 | lock和latch | innodb的锁)

303 阅读7分钟

  开发多用户 数据库驱动应用的难点:一方面要最大程度利用数据库的并发访问,另一方面还要保证每个访问的操作或者获取数据是成功&正确的。为此就有了锁的机制,这也是数据库区别于文件系统的一个关键特性。文件系统的锁是互斥锁,同一时刻只会有一个进程在访问文件,同时通过下文我们可以了解到人们认为行级锁会增加开销,但实际上,只有当实现本身增加增加开销时,行级锁才会增加开销。

什么是锁

  数据库的锁是为了支持对共享资源的并发访问,提供数据的完整性和一致性。innodb引擎中使用锁的地方很多,例如最基本的数据行锁,操作缓冲池中的LRU列表,删除,添加,移动LRU列表中的元素。     

  目前市面上的数据库种类比较多,无论是关系的或者非关系的,每种数据库的锁的实现都不尽相同, 在sql层面,因为有sql标准的存在, 要熟悉使用多个数据库问题不大,对于数据库锁的实现,其实只需要对某个特定的锁实现有经验就行。

  例如:mysql的myisam,NDB Cluster存储引擎也是大家常用的存储引擎,甚至其他的数据库例如oracle,microsoft sql server等。但是他们对锁的实现是完全不同的,对于MyISAM引擎,其锁是表锁。在并发读的情况下是没有问题的,但是并发写入的时候性能就要差很多了。对于microsoft sql server2005版本之前是其实都是页锁,相对于myisam的表锁,并发性能有所提高。页锁容易实现,但是面对热点页数据的并发问题依然束手无策。2005版本之后,microsoft sql server开始支持乐观并发和悲观并发,在乐观并发下开始支持行级锁。但是在microsoft sql server看来, 锁是一种珍贵的资源,锁越多他的开销就越大,因此他存在锁升级的情况,也就是会从行级锁升级到表锁,这时的并发性能又回去了。

lock和latch

  latch也被称为闩(shuan)锁,轻量级的锁,这里的轻量级是锁的时间要求必须非常短。在innodb引擎中latch又可以分为mutex(互斥量)和rwlock(读写锁)。其目的是保证并发线程操作共享资源的正确性,并且通常没有死锁检测的机制

  lock的对象是事务,用来锁定事务流程中需要更新的数据,如果整个事务中包括select和update,只有在update时才会将需要更新的数据锁住,select是不会锁的,只是做了隔离,保证数据的一致性, 并且一般lock的对象仅在事务commit或rollback之后进行释放(不同的事务隔离级别释放的时间可能不同)。此外lock也是有死锁机制的(这里的死锁机制是指死锁检测机制,下篇文章单独讲)

locklatch
对象事务线程
保护数据库内容内存数据结构
持续时间这个事务过程临界资源
模式行锁,表锁,意向锁读写锁互斥量
死锁通过waits-for graph ,time out等机制进行死锁检测与处理无死锁检测与处理机制,仅通过应用程序加锁的顺序保证无死锁情况的发生
存在于lock Manager的哈希表中每个数据结构的对象中

  查看innodb存储引擎中的latch,可以通过命令show engine innodb mutex来进行查看

  7b2a4704925556e39228b0a6.png

   如上图,列type总是显示innodb,列name显示的是latch的信息以及在源码中的位置(行数),列Status比较复杂,在Debug模式下,除了显示os_waits,还会显示count、spin_waits、spin_rounds、os_yields、os_wait_times等信息

名称说明
countmutex被请求的次数
spin_waitsspin lock(自旋锁)的次数,innodb存储引擎latch在不能获得锁时首先进行自旋,若自旋后还不能获得锁,则进入等待状态
spin_rounds自旋内部循环得总次数,每次自旋得内部循环是一个随机数,spin_round/spain_waits表示平均每次自旋得内部循环次数
os_waits表示操作系统等待得次数,当spin lock通过自旋还不能获得latch时,则进入操作系统等待状态,等待被唤醒
os_yields进行os_thread_yield唤醒操作的次数
os_wait_times操作系统等待得时间,单位时ms

  以上信息都是比较底层的,一般是供数据库开发人员参考的。应用开发人员就当是拓展下知识面吧。

innodb的锁

  innodb存储引擎实现了如下两种标准的行级锁:

  • 共享锁(Slock):允许事务读一行数据    
  • 排他锁(Xlock):允许事务删除或更新一行数据   两个事务针对同一行数据的共享锁是可以同时持有的,因为读取并没有改变行数据,这种情况称为锁兼容。但若有其他的事务想要获得这一行数据的排它锁,则需要等待前两个事务释放行r上的共享锁,这种情况称为锁不兼容。兼容性如下图:
XS
X不兼容不兼容
S不兼容兼容

  innodb存储引擎支持同一个事务中多粒度锁定,意味着同一个事务可以同时持有行级锁和表锁。innodb的意向锁(Intention Lock)支持在不同粒度上进行加锁操作,意向锁是将锁定的对象分为多个层次,意味着事务希望在更细粒度上进行加锁 微信图片编辑_20220306093057.jpg

  如上图, 我们可以就看出如果A事务想对表1的r记录加排它锁,那么就需要对r记录所在的页,表,库加意向锁。这是一个由上而下的过程。如果B事务在对r记录加锁之前,C事务已经对表1加上了共享锁,这时B事务相对表1增加意向锁,由于锁不兼容,所以该事物需要等待C事务锁操作的完成。

  innodb存储引擎的意向锁实质为表级别的锁。设计目的只要是为了在一个事务中揭示下一行将请求的锁类型。其支持两种意向锁:

  • 意向共享锁(IS lock):事务想要获得一张表中某几行的共享锁(表级锁)
  • 意向排他锁(IX Lock):事务想要获得一张表中某几行的排它锁(表级锁) 由于innodb存储引擎支持的是行级别的锁,因此意向锁其实不会阻塞除全表扫以外的任何请求。故表级别意向锁与行级锁的兼容性如图:
ISIXSX
IS兼容兼容兼容不兼容
IX兼容兼容不兼容不兼容
S兼容不兼容兼容不兼容
X不兼容不兼容不兼容不兼容

  从上图可以看出意向锁之间都是互相兼容的,但是与表级读写锁之间大部分都是不兼容的,意向锁不会与行级的读写锁互斥

读到这里可能就有问题了,为什么说意向锁不会与行级的读写锁互斥?

  • 我们可以反方向思考,三个事务T1,T2,T3分别想对某张表中的记录行r1,r2,r3进行修改,在实际的应用场景中这是很常见的并发场景。这三个事务目前同时对该表加意向写锁,因为意向锁之间是兼容的,所以这一步没问题,接下来增加行级排它锁,三个事务顺利执行完成。

小结

  本文旨在带大家,同时也带我了解下innodb存储引擎的锁基本知识,通过本文我们了解到了innodb的锁包含了共享锁和排他锁,在表级别上有意向锁。同时也了解了这几种锁的兼容情况, 这点在我们推理锁的执行流程上是有帮助的。