原理介绍
在MySQL OLTP场景下,大量并发的DML语句(Insert, Update, Delete)会访问lock_sys->mutex全局锁保护的关键数据结构,导致锁竞争激烈,性能下降。为了解决这个问题,鲲鹏BoostKit提供了采用细粒度hash桶锁来替换全局锁的优化特性,能够减少锁冲突,提高并发度。
MySQL中的每个表和行都可以看作是一种资源,事务可以请求访问资源。但是并发的事务对资源的访问可能造成冲突,因此MySQL设计了Lock-sys用于管理对表和行的访问。
Lock-sys会维持多个队列用于存储事务对资源的占用情况,每当有一个新请求需要申请某个资源时,Lock-sys会在相应队列里查询这个资源是否已被占用。不管请求的资源是否被占用,Lock-sys都会将锁请求插入到相应的队列中,分别标记为已授权或等待锁请求。为了支持并发操作,上述查询和插入过程需要对队列加锁。
数据库中,lock和latch有时都被称为锁,但意义不同:
- lock是用于锁定数据库对象,如表、行。
- latch是用于保护内存数据结构。
在过去,所有队列的访问均由一个latch管理,这意味着即使只要访问一个队列,其他所有的队列也会被锁住。这种实现方式在高并发场景下效率低下,为了解决这个问题,本特性引入了一种更细粒度的latch锁定方法。
新的latch锁定方法是在原来全局大锁的基础上,将队列分组为固定数量的shard,每个shard由自己的mutex保护。为了能够高效地锁住所有的shard,新特性中的全局大锁(global_latch)被设计为读写锁。对各个队列进行访问,需要先获取global latch的共享锁,再获取相应shard的mutex。这种实现方式类似于MySQL访问一条记录时,先对表加意向锁,再对相应记录加锁。在一些特殊场景下需要锁住所有队列,这时获取global latch的排他锁即可。这种实现方式的大体思路基于:大多数操作涉及一个或两个Lock-sys队列,并且独立于对其他队列进行的操作。global latch与其管理对象的关系如下图所示。
新的锁定方式的效率提升如下图所示。在过去,如果事务A和事务B分别要访问队列1和队列2,由于都需要对全局大锁上锁,这时事务B就会被阻塞;而在新特性中,事务A和事务B可以同时申请global latch的共享锁,再分别申请mutex1和mutex2,也就是说事务A和事务B可以同时进行,并发度提升。
访问两个队列以获取两条记录涉及以下步骤:
- 对global_latch执行s-latch
- 标识记录所属的2个页
- 标识包含给定页的队列的2个哈希桶
- 标识包含这两个桶的2个shard id
- 按地址顺序对两个分片的mutex上锁
以上所有步骤(除步骤2外,因为我们通常已经知道这些页)都是借助一行代码实现的:
1
|
locksys::Shard_latches_guard guard{*block_a, *block_b}; |
如果要“stop-the-world”,只需通过以下代码对global latch上x-latch:
1
|
locksys::Exclusive_global_latch_guard guard{}; |
为了使用友元保护器类,如Shard_latches_guard,该类不会公开太多的公共函数。