zoukankan      html  css  js  c++  java
  • 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锁的情况。

        

        

          

                  

  • 相关阅读:
    解决:Could not resolve archetype org.apache.maven.archetypes
    Spring MVC配置MyBatis输出SQL
    Spring集成MyBatis 通用Mapper以及 pagehelper分页插件
    关于SpringMVC或Struts2接受参数接收不到的原因
    配置quartz启动时就执行一次
    ajaxFileUpload进行文件上传时,总是进入error
    spring mvc注入配置文件里的属性
    java中将一个文件夹下所有的文件压缩成一个文件
    flume failed to start agent because dependencies were not found in classpath
    ubuntu不能安装pip unable to install pip in unbuntu
  • 原文地址:https://www.cnblogs.com/xpchild/p/3789068.html
Copyright © 2011-2022 走看看