mysql data_locks_MySQL数据结构分析—THR_LOCK

目的

MySQL多线程锁数据结构THR_LOCK,是支撑MySQL层锁的基础结构,也是MySQL与存储引擎层在锁控制上的衔接结构。对于多线程的锁控制,直接关系到数据库的并发问题。本文将分析MySQL在多线程锁数据结构的设计和实现,以便于进一步深入了解MySQL层以及存储引擎层的锁。

数据结构

MySQL多线程锁的数据结构,在include/thr_lock.h和mysys/thr_lock.c源码文件中。THR_LOCK数据结构相关的结构还包括:thr_lock_type、THR_LOCK_INFO、THR_LOCK_DATA数据结构。具体定义及详细分析如下所示:

enumthr_lock_type { TL_IGNORE=-1,

TL_UNLOCK,  /* UNLOCK ANY LOCK */

/* Parser only! At open_tables() becomes TL_READ or TL_READ_NO_INSERT depending on the binary log format (SBR/RBR) and on the table category (log table). Used for tables that are read by statements which modify tables. */

TL_READ_DEFAULT,

TL_READ,  /* Read lock */

TL_READ_WITH_SHARED_LOCKS,

/* High prior. than TL_WRITE. Allow concurrent insert */

TL_READ_HIGH_PRIORITY,

/* READ, Don't allow concurrent insert */

TL_READ_NO_INSERT,

/* Write lock, but allow other threads to read / write. Used by BDB tables in MySQL to mark that someone is reading/writing to the table. */

TL_WRITE_ALLOW_WRITE,

/* WRITE lock used by concurrent insert. Will allow READ, if one could use concurrent insert on table. */

TL_WRITE_CONCURRENT_INSERT,

/* Write used by INSERT DELAYED. Allows READ locks */

TL_WRITE_DELAYED,

/* parser only! Late bound low_priority flag. At open_tables() becomes thd->update_lock_default. */

TL_WRITE_DEFAULT,

/* WRITE lock that has lower priority than TL_READ */

TL_WRITE_LOW_PRIORITY,

/* Normal WRITE lock */

TL_WRITE,

/* Abort new lock request with an error */

TL_WRITE_ONLY};

typedefstruct st_thr_lock_info

{

pthread_t thread;

my_thread_id thread_id;

} THR_LOCK_INFO;

typedefstruct st_thr_lock_data {

THR_LOCK_INFO *owner;

struct st_thr_lock_data *next,**prev;

struct st_thr_lock *lock;

mysql_cond_t *cond;

enum thr_lock_type type;

void *status_param;  /* Param to status functions */

void *debug_print_param;

struct PSI_table *m_psi;

} THR_LOCK_DATA;

structst_lock_list {

THR_LOCK_DATA *data,**last;

};

typedefstruct st_thr_lock {

LIST list;

mysql_mutex_t mutex;

struct st_lock_list read_wait;

struct st_lock_list read;

struct st_lock_list write_wait;

struct st_lock_list write;

/* write_lock_count is incremented for write locks and reset on read locks */

ulong write_lock_count;

uint read_no_write_count;

void (*get_status)(void*, int);  /* When one gets a lock */

void (*copy_status)(void*,void*);

void (*update_status)(void*);  /* Before release of write */

void (*restore_status)(void*);  /* Before release of read */

my_bool (*check_status)(void *);

} THR_LOCK;

thr_lock_type枚举类型结构中,定义了多线程锁的类型。多线程锁的优先级为:WRITE_ALLOW_WRITE,WRITE_CONCURRENT_INSERT,WRITE_DELAYED,WRITE_LOW_PRIORITY,READ,WRITE,READ_HIGH_PRIORITY,WRITE_ONLY。特别的,锁类型TL_READ_NO_INSERT与TL_WRITE_CONCURRENT_INSERT不能共存;锁类型TL_WRITE_ALLOW_WRITE和TL_WRITE_ONLY锁互斥;锁类型TL_WRITE_LOW_PRIORITY的优先级低于TL_READ;锁类型TL_READ_HIGH_PRIORITY优先级高于TL_WRITE;获得TL_WRITE_ONLY时,其他任何锁请求都无法获取。

THR_LOCK_INFO数据结构定义了线程相关的信息,包括thread线程和thread_id线程id两个参数。

THR_LOCK_DATA数据结构定义了多线程锁的数据信息,包括线程信息owner,类型是THR_LOCK_INFO;next和prev指针为当前数据信息的下一个和上一个数据信息,是双向链表结构;lock是THR_LOCK数据类型的锁信息;多线程并发的条件变量cond;锁类型type,取值为thr_lock_type中的值;status_param是THR_LOCK数据结构中状态函数的参数;debug_print_param是DEBUG状态下输出的参数;m_psi是系统表的接口。通过以上分析,可以清晰了解到,THR_LOCK_DATA数据结构是一个双向链表结构。

此外,还定义了st_lock_list结构,是THR_LOCK_DATA双向链表的头信息,其中data和last指针分别指向THR_LOCK_DATA双向链表的头和尾信息。其结构图如下所示:

28c1eb3c0a2da0d37fc83da0e1f75ba2.png

图1 st_lock_list结构图

THR_LOCK类型是多线程的锁数据结构,主要包括双向链表数据类型的锁对象list;互斥锁mutex;四个数据信息列表读等待列表read_wait、读列表read、写等待列表write_wait、写列表write;写锁的数目write_lock_count;只是读操作,但没有写操作的数目read_no_write_count;五个与状态信息操作相关的函数get_status、copy_status、update_status、restore_status和check_status。

以上仅对这些结构及其相关的参数进行了简要的说明,详细的操作将在源码分析中,给出详细的介绍。

源码实现

THR_LOCK相关的操作,主要对核心的处理方法进行详细的分析,对于简单处理逻辑,可以直接参考源码,不再赘述。

thr_lock函数

thr_lock()函数的具体逻辑如下图所示。如果lock_type小于等于TL_READ_NO_INSERT的值,说明当前请求的锁类型为读锁(READ lock),则执行请求读锁处理逻辑;否则请求的为写锁(WRITE lock),执行请求写锁处理逻辑。如果在锁处理逻辑过程中(包括读锁和写锁),未获得相应的锁,则调用wait_for_lock()函数,等待获得相应的锁。具体锁请求处理逻辑和wait_for_lock()函数的逻辑参考相应的逻辑分析过程。

640004e963513c99ff6497a3bfa22f55.png

图2 thr_lock()流程图

请求读锁(Request for READ lock)的处理逻辑如下图所示。具体的,如果当前写队列(lock->write)中有数据,并且写队列数据的已经获得了写锁,并且请求的锁类型(lock_type)不是TL_READ_NO_INSERT或者写队列数据的写锁类型不是TL_WRITE_CONCURRENT_INSERT(两种锁类型是互斥的),则将当前数据插入到写队列(lock_read)的末尾。如果写队列数据的写锁类型为TL_WRITE_ONLY(写独占式锁),则当前不能获得读锁,则直接返回。

否则,当前写队列为空,如果写等待队列也为空(说明没有任何写锁及写锁等待),或者写等待队列数据的锁类型不是重要的写锁(除了TL_WRITE和TL_WRITE_ONLY类型),或者读锁为TL_READ_HIGH_PRIORITY(该锁的优先级高于TL_WRITE),或者已经获得了读锁,那么同样将当前数据插入到写队列的末尾。如果此时请求的锁类型为TL_READ_NO_INSERT,那么变量read_no_write_count自增。

如果当前写队列中有活跃的写锁或者有重要的写锁等待,那么此时读锁需要等待写锁释放后,再获取读锁。因此,此时调用wait_for_lock()函数,等待获取读锁。

9bc192a5da01a23e3bb36227bf9ca493.png

图3 Request for READ lock处理逻辑

请求写锁(Request for WRITE lock)的处理逻辑如下图所示。具体的详细处理逻辑包括三个过程:

第一个过程:如果请求的锁类型是延迟写锁(TL_WRITE_DELAYED),而写队列(lock->write)数据的锁是只写锁(TL_WRITE_ONLY),那么不能获得延迟写锁,直接返回。而如果请求延迟写锁,并且写队列或者读队列有数据信息,那么将数据添加到写等待队列(lock->write_wait)中。如果请求的锁类型是写并发插入的锁类型(TL_WRITE_CONCURRENT_INSERT),那么数据的锁类型和请求的锁类型将赋值为thr_upgraded_concurrent_insert_lock全局变量的值,该值默认是TL_WRITE锁类型,如果设置了系统全局变量low_priority_updates,那么该值为TL_WRITE_LOW_PRIORITY。

第二个过程:如果已经获得了写锁,即写队列中有数据信息,并且锁类型为TL_WRITE_ONLY,那么无法获得写锁,直接返回。如果请求锁的类型和写队列中数据的锁类型为TL_WRITE_ALLOW_WRITE(因为TL_WRITE_ALLOW_WRITE锁类型允许其他线程读写操作),并且无写等待队列(如果有写队列需要首先处理写等待队列中的请求)。或者当前数据信息已经获得了写锁,那么将当前数据信息添加到写队列的末尾。

第三个过程:如果写队列和写等待队列都没有数据信息,并且请求写锁类型为延迟写锁(TL_WRITE_DELAYED),以及变量lock->read_no_write_count的值为0(表明当前多线程中没有获得TL_READ_NO_INSERT锁类型的操作),那么数据直接添加到写队列中,而不是过程一中请求延迟写锁时,将数据添加到写等待队列中。原因是当前没有任何写锁,不需要延迟写。

0cc5a71db0c4e08d7d1b45883bca167c.png

图4 Request for WRITE lock处理逻辑

wait_for_lock函数

wait_for_lock()函数主要是在请求锁时,由于当前写队列中有活跃的写锁,或者写等待队列中有高优先级的写锁,导致无法获取锁时,将会等待锁释放后请求锁。具体逻辑是:如果当前线程没有被终止,或者获取锁的数据信息在等待列表中,那么调用mysql_cond_timedwait()函数条件等待,如果等待超时或者条件变量释放,那么锁等待结束。如果锁等待超时,那么将数据信息从等待队列中删除,调用wake_up_waiter()函数,唤醒数据请求的锁。否则,数据信息获取锁被终止。如果该函数的流程图如下所示:

3d13259226ba5100aa8fa4c3caaacff4.png

图5 wait_for_lock()流程图

wake_up_waiters函数

wake_up_waiters()函数的流程图如下所示。具体的,如果当前写队列中有活跃的写锁,则说明不需要唤醒,直接退出程序。否则,从写等待队列中取出等待写锁的数据,如果没有活跃的读锁,则释放写等待队列中的写锁,并释放可能的读锁。

e055aa6d831bc13b5443dd2faad96576.png

图6 wake_up_waiter()流程图

如果有活跃的读锁,并且如果当数据的锁类型为TL_WRITE_DELAYED、TL_WRITE_ALLOW_WRITE、TL_WRITE_CONCURRENT_INSERT或者允许读锁,判断条件如下所示。从判断条件可以看出,只有当data不为NULL,并且data->type是小于TL_WRITE_DELAYED时,因为data是写等待队列的数据,因此只有TL_WRITE_DELAYED、TL_WRITE_ALLOW_WRITE和TL_WRITE_CONCURRENT_INSERT三种类型的锁。如果锁类型是TL_WRITE_DELAYED时,或者read_no_write_count参数为0时,即没有TL_READ_NO_INSERT类型的读锁时。则唤醒写锁,并且与读锁共存。

if(data &&

(lock_type=data->type) <= TL_WRITE_DELAYED &&

((lock_type != TL_WRITE_CONCURRENT_INSERT &&

lock_type != TL_WRITE_ALLOW_WRITE) ||

!lock->read_no_write_count))

否则,如果写等待队列中无等待写锁,并且读等待队列中有数据时,调用free_all_read_locks()函数释放所有读锁。

wake_up_waiter()函数处理逻辑中,包含两个重要的处理过程,分别是释放写锁(Release write-locks)处理过程和唤醒写锁(start WRITE locks with READ locks)处理过程。以下分别对这两个过程进一步分析:

1、释放写锁(Release write-locks)处理过程

Release write-locks处理过程主要逻辑如下所示:

9267618a53d2c931b3c63f1bec80bc01.png

图7 Release write-locks处理过程

从以上逻辑处理过程可知,如果数据data不是NULL,并且data的锁类型不是TL_WRITE_LOW_PRIORITY(该锁类型优先级低于TL_READ),或者读等待队列为空(如果锁类型为TL_WRITE_LOW_PRIORITY,那么读等待队列为空时,锁类型可以获得),或者读等待队列数据的类型小于TL_READ_HIGH_PRIORITY(如果请求的锁类型为TL_WRITE_LOW_PRIORITY,并且读等待队列不为空,那么读等待队列数据的锁类型优先级要低于写锁的优先级,锁类型TL_READ_HIGH_PRIORITY和TL_READ_NO_INSERT的优先级要高于某些写锁的优先级)。因为锁类型TL_WRITE_LOW_PRIORITY的优先级低于TL_READ,因此如果当前有读锁,那么data请求写锁TL_WRITE_LOW_PRIORITY的话,不需要等待,可以直接处理。

如果不满足判定条件,那么如果读等待队列不为空的话,调用free_all_read_locks()函数释放所有读锁。

如果满足判定条件,首先判断写锁计数是否大于最大写锁数(unsigned long类型)。如果大于该值,那么调用free_all_read_locks()函数释放读等待队列中的所有读锁。否则,将data从写等待队列中删除,添加到写队列中,并信号通知等待线程。如果data的锁类型或锁等待队列中的锁类型不是TL_WRITE_ALLOW_WRITE(该锁类型允许其他线程进行读写操作,如果为该锁类型时,可以继续释放写锁),或者写等待队列为空的情况下,退出循环。否则,继续从写等待队列中取数据,释放写锁。

2、唤醒写锁(start WRITE locks with READ locks)处理过程

唤醒写锁(start WRITE locks with READ locks)的详细处理逻辑如下所示:

bd5dd30840c30eb057f52695c673e4c3.png

图8 start WRITE locks with READ locks处理过程

由以上流程图可知,如果当前锁类型为TL_WRITE_CONCURRENT_INSERT,那么升级锁为TL_WRITE,并释放所有读等待队列中的读锁。这是之所以升级写锁,是由于TL_WRITE_CONCURRENT_INSERT锁类型允许READ锁,但是为了避免与读锁类型TL_READ_NO_INSERT发生冲突,所以需要升级写锁为TL_WRITE。

否则,data的锁类型只有TL_WRITE_DELAYED和TL_WRITE_ALLOW_WRITE两种类型,那么将data从写等待队列中删除,添加到写队列中,并信号通知等待线程。如果锁类型和数据的锁类型为TL_WRITE_ALLOW_WRITE,并且写等待队列不为空的情况下,继续从写等待队列中释放写锁。否则,释放读等待队列中的所有读锁。

thr_unlock函数

thr_unlock()函数是多线程释放数据data上锁的处理逻辑,详细流程如下图所示。首先将data从锁队列中删除,如果锁类型是TL_READ_NO_INSERT,那么锁的read_no_write_count计数减1。最终调用wake_up_waiters()函数,唤醒等待锁的请求。

6cf1c03b71ac9446833964517cb96c52.png

图9 thr_unlock()流程图

除了以上核心函数处理过程之外,THR_LOCK还提供了thr_multi_lock()函数处理获取多个锁;thr_multi_unlock()释放获取的多个锁;thr_lock_merge_status()将同一个表的共享相同的状态信息,即同一个表的status_param参数的值相同,主要是针对MyISAM和Maria存储引擎;thr_abort_locks()是终止所有线程的锁请求,写队列中的锁升级为TL_WRITE_ONLY锁,从而阻止新的线程请求锁;thr_abort_locks_for_thread()用于终止给定线程的所有锁请求;thr_downgrade_write_lock()将高级别的写锁降级为低级别的写锁;thr_upgrade_write_delay_lock()升级TL_WRITE_DELAYED写锁为高级别的写锁类型;thr_reschedule_write_lock()将高级别的写锁降级到TL_WRITE_DELAYED锁。这些函数的处理逻辑较简单,可以参考源码的实现,不再赘述。

结论

以上是对THR_LOCK数据结构源码的详细分析,通过分析可知,MySQL细粒度的锁类型,使得多线程对不同锁类型的请求时,可以同时获得锁,但由于锁的优先级关系和锁之间存在互相排斥,也可以保证通过锁对资源的控制,实现多线程有条不紊的操作数据。并且细粒度控制锁,可以更大程度地提高多线程的并发性。

此外,推荐《MySQL数据库上层加锁逻辑》博文,该文系统的测试和说明MySQL数据库上层加锁的逻辑,其中包含THR_LOCK相关的操作。从逻辑和实际SQL测试,有利于了解MySQL的上层锁机制。结合本文对THR_LOCK的源码分析,可以更进一步的了解底层多线程锁控制的机制,对THR_LOCK上层锁控制源码的分析,将在之后进行详细的分析。

参考

1、《MySQL数据库上层加锁逻辑》:http://www.mysqlops.com/2011/12/05/mysql-logic.html

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值