计算机系统应用教程网站

网站首页 > 技术文章 正文

MySql数据库锁机制--mutex互斥量详解

btikc 2024-10-12 10:46:19 技术文章 7 ℃ 0 评论

概述

锁是数据库系统区别文件系统的关键特性。锁机制用于管理对共享资源的并发访问。

Innodb引擎会在行级别上对表数据进行上锁。不过存储引擎也会在数据库内部的其他多个地方使用锁,从而允许对多个不同资源提供并发访问。例如,操作缓存持中的LRU列表,删除,添加,移动LRU列表,为了保证一致性,必须要有锁介入。这也间接了证明了数据库系统使用锁是为了支持共享资源进行并发访问,以达到提供数据的完整性和一致性的目的。

不过今天主要介绍mutex,下面先看下latch相关概念。


1、latch

Latch一般称为闩锁(轻量级的锁),因为其要求锁定的时间必须非常短。若持续的时间长,则应用的性能会非常差,在InnoDB引擎中,Latch又可以分为mutex(互斥量)和rwlock(读写锁)。其目的是用来保证并发线程操作临界资源的正确性,并且通常没有死锁检测的机制。latch可以通过命令show engine innodb mutex来进行查看。

Lock的对象是事务,用来锁定的是数据库中的对象,如表、页、行。并且一般lock的对象仅在事务commit或rollback后进行释放(不同事务隔离级别释放的时间可能不同)。


2、mutex互斥量

数据库中的Mutex量指的是一种用于保护一些临界资源的使用的信号量。当有线程需要使用这 些临界资源时,会请求获得mutex量,请求成功的线程进入临界区,而请求失败的线程只能等待它释放这个mutex。互斥信号量在计算机软件层面以上可以 看作是实现并发操作的一个原子动作,但在数据库(操作系统)这种高并发多线程的基础软件中,需要精心设计以获得高吞吐量和良好响应时间。


3、Innodb的同步锁机制

Innodb封装了mutex和rw_lock结构来保护内存的变量和结构,进行多线程同步,考虑可移植性, mutex使用lock_word或者OS mutex来保证原子操作,并使用event条件变量进行阻塞和唤醒操作。

os_event_t event; 
volatile lock_word_t lock_word; 
os_fast_mutex_t os_fast_mutex;

4、Innodb同步锁引入的数据结构和开销

4.1、全局mutex链表

Innodb引入了一个全局的链表ut_list_base_node_t mutex_list,并使用一个单独的mutex来保护链表。 所有的mutex在create或者free的时候来修改链表,有了全局链表,也使统计汇总有了可能性,参考命令“show engine innodb mutex”. 虽然需要维护一个全局的链表,但这并不会影响太多的性能,因为大部分的mutex的生命周期都是从Innodb启动一直到shutdown。

4.2、统计信息

mutex的结构中,有几个统计信息:

count_os_wait:请求mutex进入等待的次数
count_using: 请求mutex的次数
count_spin_loop: 请求mutex时spin的轮数
count_spin_rounds: 请求mutex的spin次数
count_os_yield:请求mutex spin失败后os等待次数
lspent_time: 统计等待mutex的时间

lock mutex的主要步骤:

1)首先trylock mutex,如果没有获取到mutex,并不马上进行wait,而是进行spin。

2)尝试spin,如果在SYNC_SPIN_ROUNDS次后,仍然没有lock,那么就进入等待队列,等待唤醒。 对于mutex的统计,MySQL使用了performance_schema的等待事件来代替,即:

4.3、全局等待队列

Innodb为所有的等待状态的线程准备了一个队列,如果获取mutex失败,那么就申请一个cell,进入阻塞状态,等待signal。全局等待队列 sync_primary_wait_array,有了这个队列,Innodb就可以对这些wait的线程进行统计,比如long semaphore waits就是根据这个队列进行的查询。


5、Mysql中的mutex实现机制

大致描述如下(基于Windows操作系统):

数据结构:在mutex结构中,有:

1)event:操作系统提供的信号量;
2)lock_word:锁,这是一个机器字长的数,为1说明mutex有人使用,为0则没有。
3) waiters:标志有无人在等待这个mutex,同样是机器字长的数
4) list:一个list把系统所有的mutex连起来,这里不用关注

一个线程开始请求一个mutex步骤:

1)Test And Set,使用一条汇编指令来尝试获得这个mutex,这个动作是计算机原子的。

2)如果操作成功,则mutex中的lock_word被置成1,本线程返回,可以进行临界区操作。

3)如果失败,开始一个spinLock的过程(所谓spin就是指去扭转一个门把手,如果 扭不开把手会转回来,然后就继续去扭直到它开为止),不停循环并且读lock_ward的值,发现为0了(注意,是脏读,所以去 TestAndSet仍然可能失败)则再次去执行TestAndSet。若置位成功了,则返回。

4)spinLock循环到了一定次数,不能继续spin下去了,否则太消耗CPU资源,此时本线程睡眠,醒来后再尝试一次TestAndSet,若成功了,则返回。

5)进入全局等待队列sync_primary_wait_array中,通过队列中的 os_mutex保证此时在队列中的操作是唯一的。找到一个空的Cell,把它的对象指针object指向自己,等待线程ID设为自己的ID 号,request_type设置为MUTEX,waiting设为FALSE,同时调用操作系统接口reset互斥量mutex上的event(API: ResetEvent),退出全局等待队列。

6)把mutex中的waiters设为1,标志有人在等待这个mutex,这个动作使用了一个volatile 的指针来改这个值,以保证这个值被立即写回内存。

7)这时再尝试去做几次TestAndSet(垂死挣扎),若成功了,回去把全局等待队列sync_primary_wait_array中自己刚拿到的cell清空掉,并返回。

8)进入全局等待队列sync_primary_wait_array,把自己的cell里的waiting标识为TRUE,拿到event后退出全局队列。

9)等待在这个event上直到被唤醒(API:WaitForSingleObject),被唤醒后立即进入全局等待队列里放掉自己的cell,再继续从1)开始。

线程释放mutex的步骤:

1)ResetLock,同样使用一条汇编指令将mutex中的lock_word设置为0;

2)检查mutex上的waiters是否为1,如果是,则执行下面3)4)两步的唤醒例程,否则返回。

3)先将waiters设置为0,这同样使用一个volatile的指针来做。

4)将mutex上的event唤醒(API:SetEvent)。


总结

数据库中的mutex是一个需要考虑硬件实现,并且要考虑多种资源的平衡因素的关键性数 据结构。但MySql的实现个人认为过于复杂了,Mutex作为一个可以看作是数据库级别的最轻量级并发控制器,不应该被考虑用于平均时间过长的临界区。 性能才是mutex应该衡量的最关键因素。

后面会分享更多devops和DBA方面的内容,感兴趣的朋友可以关注下~

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表