Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1745351
  • 博文数量: 107
  • 博客积分: 1715
  • 博客等级: 上尉
  • 技术积分: 3168
  • 用 户 组: 普通用户
  • 注册时间: 2012-04-18 18:42
个人简介

阿里巴巴DBA,原去哪儿网DBA。专注于MySQL源码研究、DBA运维、CGroup虚拟化及Linux Kernel源码研究等。 github:https://github.com/HengWang/ Email:king_wangheng@163.com 微博 :@王恒-Henry QQ :506437736

文章分类

全部博文(107)

文章存档

2014年(2)

2013年(38)

2012年(67)

分类: Mysql/postgreSQL

2013-01-12 13:59:14

目的

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

数据结构

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

 

enum thr_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};

typedef struct st_thr_lock_info

{

  pthread_t thread;

  my_thread_id thread_id;

} THR_LOCK_INFO;

typedef struct 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;

struct st_lock_list {

  THR_LOCK_DATA *data,**last;

};

typedef struct 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_WRITEWRITE_CONCURRENT_INSERTWRITE_DELAYEDWRITE_LOW_PRIORITYREADWRITEREAD_HIGH_PRIORITYWRITE_ONLY。特别的,锁类型TL_READ_NO_INSERTTL_WRITE_CONCURRENT_INSERT不能共存;锁类型TL_WRITE_ALLOW_WRITETL_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_INFOnextprev指针为当前数据信息的下一个和上一个数据信息,是双向链表结构;lockTHR_LOCK数据类型的锁信息;多线程并发的条件变量cond;锁类型type,取值为thr_lock_type中的值;status_paramTHR_LOCK数据结构中状态函数的参数;debug_print_paramDEBUG状态下输出的参数;m_psi是系统表的接口。通过以上分析,可以清晰了解到,THR_LOCK_DATA数据结构是一个双向链表结构。

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

 

1 st_lock_list结构图

 

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

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

源码实现

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

thr_lock函数

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

 

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_WRITETL_WRITE_ONLY类型),或者读锁为TL_READ_HIGH_PRIORITY(该锁的优先级高于TL_WRITE),或者已经获得了读锁,那么同样将当前数据插入到写队列的末尾。如果此时请求的锁类型为TL_READ_NO_INSERT,那么变量read_no_write_count自增。

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

 

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锁类型的操作),那么数据直接添加到写队列中,而不是过程一中请求延迟写锁时,将数据添加到写等待队列中。原因是当前没有任何写锁,不需要延迟写。

 

4 Request for WRITE lock处理逻辑

 

wait_for_lock函数

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

 

5 wait_for_lock()流程图

 

wake_up_waiters函数

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

 

6 wake_up_waiter()流程图

 

       如果有活跃的读锁,并且如果当数据的锁类型为TL_WRITE_DELAYEDTL_WRITE_ALLOW_WRITETL_WRITE_CONCURRENT_INSERT或者允许读锁,判断条件如下所示。从判断条件可以看出,只有当data不为NULL,并且data->type是小于TL_WRITE_DELAYED时,因为data是写等待队列的数据,因此只有TL_WRITE_DELAYEDTL_WRITE_ALLOW_WRITETL_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处理过程主要逻辑如下所示:

 

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_PRIORITYTL_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)的详细处理逻辑如下所示:

 

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_DELAYEDTL_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()函数,唤醒等待锁的请求。

 

9 thr_unlock()流程图

 

       除了以上核心函数处理过程之外,THR_LOCK还提供了thr_multi_lock()函数处理获取多个锁;thr_multi_unlock()释放获取的多个锁;thr_lock_merge_status()将同一个表的共享相同的状态信息,即同一个表的status_param参数的值相同,主要是针对MyISAMMaria存储引擎;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数据库上层加锁逻辑》

阅读(4434) | 评论(4) | 转发(1) |
给主人留下些什么吧!~~

gpfeng_cs2013-04-24 12:06:25

king_wangheng:THR_LOCK_DATA结构是双向链表结构应该没有错误,通过next可以向后遍历,通过prev可以向前遍历,符合双向链表的特征。而hlist应该主要指带头结点的双向链表,而这里的THR_LOCK_DATA本身是一个双向链表结构,并定义了st_lock_list结构,分别指向双向链表的头和尾。个人认为,这些都是对双向链表的一种扩展。

我的理解:prev保存的是前一个节点next指针的地址,是方便插入删除的,并不是用来向前遍历链表的(当然这个是可以做到的)

回复 | 举报

king_wangheng2013-04-23 23:36:54

gpfeng_cs:“通过以上分析,可以清晰了解到,THR_LOCK_DATA数据结构是一个*双向链表*结构”

这个存在问题,准确的说它是一个hlist结构,因为节点是无法找到它的prev node

参考:http://blog.csdn.net/zhanglei4214/article/details/6767288

THR_LOCK_DATA结构是双向链表结构应该没有错误,通过next可以向后遍历,通过prev可以向前遍历,符合双向链表的特征。而hlist应该主要指带头结点的双向链表,而这里的THR_LOCK_DATA本身是一个双向链表结构,并定义了st_lock_list结构,分别指向双向链表的头和尾。个人认为,这些都是对双向链表的一种扩展。

回复 | 举报

gpfeng_cs2013-04-23 17:27:53

“通过以上分析,可以清晰了解到,THR_LOCK_DATA数据结构是一个*双向链表*结构”

这个存在问题,准确的说它是一个hlist结构,因为节点是无法找到它的prev node

参考:http://blog.csdn.net/zhanglei4214/article/details/6767288

gpfeng_cs2013-04-22 19:14:28

一直很想弄清楚THR_LOCK,对于其用途/何时使用、和线程,innodb record/table lock之间的关系,在博客中介绍得较少,希望王兄有时间再写一篇帮忙解惑~