• MySQL锁系列2 表锁


    上一篇介绍了MySQL源码中保护内存结构或变量的锁,这里开始介绍下MySQL事务中的表锁。

    注1: 在表锁的实现上面,使用【mutex+condition+queue】的结构实现并发,阻塞,唤醒的表锁功能。

    注2: 本文进行的一些实验,重要的配置项:

    1.  autocommit=0
    2.  tx_isolation=read-commited
    3.  engine=innodb

    1. MySQL加锁的顺序:

          

    这里包括了一个sql在执行的过程中,包括commit,所添加和释放跟事务相关的锁以及加不同锁的加锁顺序,这一篇先重点介绍一下MySQL的表锁。

    2. MySQL的表锁

        注:通过测试看到,MySQL的表锁,是落入innodb层的代码中实现的。

    2.1 重要的数据结构

          struct st_thr_lock_info

          struct st_thr_lock_data

          struct st_thr_lock

          通过这三个主要的struct,来实现表锁,这三者之间的关系是:

    说明:

          1. 每一张表,在打开的时候,创建一个innobase_share对象,并初始化一个st_thr_lock结构进行关联,所有请求这个表的表锁,需要关联这个对象

       2. 每一次sql请求,在open table的过程中,会创建一个table,handler对象,这个handler会初始化一个st_thr_lock_data,关联st_thr_lock结构。

    2.2 测试

         1. 测试用例:

            session1:        session2:

            lock table pp write;    select * from pp;

      2. 测试的主要步骤:

         1. open table

         2. lock table

          3. 表锁相关的case场景:

          1. 加锁

          2. 阻塞

          3. 唤醒 

      2.2.1 open table

      open table的细节也可以参考:open table

      这里主要介绍innodb层在open table时创建的主要的数据结构:

      1. innobase_share

              innodb层表示一个table的结构,包括初始化一个thr_lock,所有请求表锁的关联结构

      2. ib_table

        innodb层表示一个table的统计信息

      3. handler

        提供给sever层table的结构,所有对innodb层的操作,都通过handler,并初始化了一个st_thr_lock_data

      

    主要函数调用栈:

          open_table

             open_table_from_share

          ha_innobase::open: get_share/thr_lock_init/thr_lock_data_init

            1. 在第一次open 这个表时,创建innobase_share, 初始化THD_LOCK, 初始化 ib_table

            2. 初始化handler,初始化THD_LOCK_DATA。

    说明:

      1. 所有的innobase_share结构,保存到一个全局hash表中:innobase_open_tables,全局共享

      2. open结束后,创建完成的所有的相关的数据结构关联图如下:

        

         解释: 红色的部分是表锁的关键结构,mutex用于保护thr_lock queue结构,所有的加锁,wait 锁,线层结构都需要进入thr_lock中的queue,一共有四个queue: read,read_wait, write, write_wait.

          每一个handler都关联thread的一个condition,所有的wait,wakeup,都使用这个condition来完成,这样可以实现定向唤醒,避免广播。

    2.2.2 lock table

        session1:请求的lock_type = TL_WRITE

        session2:请求的lock_type = TL_READ

    一共的thr_lock_type有14种,分别是:

    tl_ignore=-1,
    tl_unlock,            
    tl_read_default,
    tl_read,            
    tl_read_with_shared_locks,
    tl_read_high_priority,
    tl_read_no_insert,
    tl_write_allow_write,
    tl_write_concurrent_insert,
    tl_write_delayed,
    tl_write_default,
    tl_write_low_priority,
    tl_write,
    tl_write_only

    主要函数调用栈:

      mysql_lock_tables:把所有table的thr_lock_data放到MYSQL_LOCK的结构中。
        get_lock_data
          ha_innobase::store_lock:把之前的lock清理掉。换成目前要请求的lock类型。

      thr_multi_lock:请求锁表

        thr_lock: 单个thr_lock_data锁表

    下面看下关键的三个步骤:锁表,阻塞,唤醒

      锁表:session 1

        session1 使用lock write来加锁,这里加的是排他锁,thr_lock结构上没有其他锁,这里会加成功,

        主要的代码:

           mysql_mutex_lock(&lock->mutex); 锁住这个表的mutex,开始进入这个表锁的串行操作。
           lock_type=TL_WRITE
           (*lock->write.last)=data;                  /* Add to running FIFO */
           data->prev=lock->write.last;
           statistic_increment(locks_immediate,&THR_LOCK_lock); 累计locks_immediate计数

      阻塞:session 2

        session 2申请pp表的read表锁,但session1已经获得的排他锁,这里会阻塞,并wait 这个线程的condition。

        wait_for_lock(wait_queue, data, 0, lock_wait_timeout)
          statistic_increment(locks_waited, &THR_LOCK_lock);  累计locks_waited计数
          (*wait->last)=data; /* Wait for lock */  加入wait队列
          data->prev= wait->last;
          wait->last= &data->next;
          mysql_cond_timedwait(cond, &data->lock->mutex, &wait_timeout);  等待这个thread的condition

                这里的condition要注意,是THD结构中的condition,线程的阻塞,不管是因为什么原因,只需要一个condition就可以完成,没有必要对于不同的锁等待,创建不同的condition。

      唤醒:session 1 unlock。

        session1:使用unlock tables操作。

        /* Unlock lock and free next thread on same lock */
        thr_unlock:
         wake_up_waiters(lock); 唤醒等待同一个lock的thread,这里需要判断lock请求的兼容模式,并且因为使用queue保存了请求wait队列,防止了饿死。
            mysql_cond_signal(cond);

    注意:

      对于innodb来说,不论autocommit的设置如何,每一个dml select结束后,都使用thr_unlock释放掉了表锁,这里的理解是:innodb倾向使用行级锁来支持事务,对于保护表metadata信息,则使用MDL来保护,所以innodb对于表锁来说,并没有使用意愿。

    后话:

      对于上面的测试,大家可以试一下,先操作session2. 后操作session1。结果是一样的,都是阻塞,但是这里阻塞在什么锁上,完全不同。

    简略测试一下:

      session 2: select * from pp : 

        open

        获取mdl锁,

        获取表锁,

        执行结束

        释放表锁: (因为autocommit=0,这里并没有释放mdl锁)

      session 1: lock table pp write

        open

        获取mdl排他锁: (阻塞:因为session2没有释放mdl锁,所以这里阻塞)

    大家可以看到,这里是因为ddl需要拿到mdl排他锁,而阻塞。

    下一篇blog,我们就来看看mdl锁的情况。

        

        

          

                  

  • 相关阅读:
    POJ 3694 Network (求桥,边双连通分支缩点,lca)
    UVA 796
    UVA 315 315
    POJ 1236 Network of Schools (有向图的强连通分量)
    【转】有向图强连通分量的Tarjan算法
    HDU 3072 Intelligence System (强连通分量)
    【转】强大的vim配置文件,让编程更随意
    WORDPRESS登录后台半天都无法访问或者是访问慢的解决方法
    phpstorm+Xdebug断点调试PHP
    PHP性能调优---PHP调试工具Xdebug安装配置教程
  • 原文地址:https://www.cnblogs.com/xpchild/p/3789068.html
Copyright © 2020-2023  润新知