http://blog.itpub.net/26515977/viewspace-1208250/   腾讯工程师
随着5.5.3引入MDL,更多的Query被“Waiting for table metadata lock”给\'炕\'了
SHOW PROCESSLIST的输出也有之前的"Locked"变得粒度更加细的\'Waiting for table metadata lock\'
引入MDL,当需要访问、修改表结构时,都需要对元数据上锁(读/写)
MDL在Server层,保护表数据结构,而非数据本身。Server比之前版本繁忙多了

MDL意味着DDL,一旦DDL被阻塞,那么面向该表的所有Query都会被挂起,包括Select,不过5.6作了改进,5.5可通过参数控制

假如没有MDL

在5.1版本,MDL的生命随语句的结束而释放,并非在事务提交后结束,意味着事务不是真正的隔离
因为同一条Query在2次执行期间倘若被插入了DDL,那么得到的结果将会不同,下面是个例子

  1. 会话1
  2.  
  3. mysql> select version();
  4. +------------+
  5. | version() |
  6. +------------+
  7. | 5.1.72-log |
  8. +------------+
  9. 1 row in set (0.00 sec)
  10.  
  11. mysql> select @@tx_isolation;
  12. +-----------------+
  13. | @@tx_isolation |
  14. +-----------------+
  15. | REPEATABLE-READ |
  16. +-----------------+
  17. 1 row in set (0.00 sec)
  18.  
  19. mysql> begin;
  20. Query OK, 0 rows affected (0.00 sec)
  21.  
  22. mysql> select * from t where id=1;
  23. +----+--------+
  24. | id | name |
  25. +----+--------+
  26. | 1 | python |
  27. +----+--------+
  28. 1 row in set (0.04 sec)
  29.  
  30. 会话2
  31.  
  32. mysql> alter table t add column comment varchar(200) default \'I use Python\';
  33. Query OK, 3 rows affected (0.02 sec)
  34. Records: 3 Duplicates: 0 Warnings: 0
  35.  
  36. 会话1
  37.  
  38. mysql> select * from t where id=1;
  39. Empty set (0.00 sec)
  40.  
  41. mysql> rollback;
  42. Query OK, 0 rows affected (0.00 sec)
  43.  
  44. mysql> begin;
  45. Query OK, 0 rows affected (0.00 sec)
  46.  
  47. mysql> select * from t where id=1;
  48. +----+--------+--------------+
  49. | id | name | comment |
  50. +----+--------+--------------+
  51. | 1 | python | I use Python |
  52. +----+--------+--------------+
  53. 1 row in set (0.00 sec)

与上面的不同,在5.5 MDL拉长了生命长度,与事务同生共死,只要事务还在,MDL就在,由于事务持有MDL锁,任何DDL在事务期间都休息染指,下面是个例子

  1. 会话1
  2.  
  3. mysql> select version();
  4. +------------+
  5. | version() |
  6. +------------+
  7. | 5.5.16-log |
  8. +------------+
  9. 1 row in set (0.01 sec)
  10.  
  11. mysql> begin;
  12. Query OK, 0 rows affected (0.00 sec)
  13.  
  14. mysql> select * from t order by id;
  15. +----+------+
  16. | id | name |
  17. +----+------+
  18. | 1 | a |
  19. | 2 | e |
  20. | 3 | c |
  21. +----+------+
  22. 3 rows in set (0.00 sec)
  23.  
  24. 会话2
  25.  
  26. mysql> alter table t add column cc char(10) default \'c lang\'; <<===Hangs
  27.  
  28. 会话3
  29.  
  30. mysql> show processlist;
  31. +----+------+-----------+------+---------+------+---------------------------------+-------------------------------------------------------+
  32. | Id | User | Host | db | Command | Time | State | Info |
  33. +----+------+-----------+------+---------+------+---------------------------------+-------------------------------------------------------+
  34. | 2 | root | localhost | db1 | Sleep | 191 | | NULL |
  35. | 3 | root | localhost | db1 | Query | 125 | Waiting for table metadata lock | alter table t add column cc char(10) default \'c lang\' |
  36. | 4 | root | localhost | NULL | Query | 0 | NULL | show processlist |
  37. +----+------+-----------+------+---------+------+---------------------------------+-------------------------------------------------------+

回滚了会话1的事务后,我们看下alter主要被谁炕了

  1. mysql> show profiles;
  2. +----------+---------------+-------------------------------------------------------+
  3. | Query_ID | Duration | Query |
  4. +----------+---------------+-------------------------------------------------------+
  5. | 1 | 1263.64100500 | alter table t add column dd char(10) default \' Elang\' |
  6. +----------+---------------+-------------------------------------------------------+
  7. 1 row in set (0.00 sec)
  8.  
  9. mysql> show profile for query 1;
  10. +------------------------------+------------+
  11. | Status | Duration |
  12. +------------------------------+------------+
  13. | starting | 0.000124 |
  14. | checking permissions | 0.000015 |
  15. | checking permissions | 0.000010 |
  16. | init | 0.000023 |
  17. | Opening tables | 0.000063 |
  18. | System lock | 0.000068 |
  19. | setup | 0.000082 |
  20. | creating table | 0.034159 |
  21. | After create | 0.000185 |
  22. | copy to tmp table | 0.000309 |
  23. | rename result table | 999.999999 |
  24. | end | 0.004457 |
  25. | Waiting for query cache lock | 0.000024 |
  26. | end | 0.000029 |
  27. | query end | 0.000009 |
  28. | closing tables | 0.000030 |
  29. | freeing items | 0.000518 |
  30. | cleaning up | 0.000015 |
  31. +------------------------------+------------+
  32. 18 rows in set (0.00 sec)

显然,在"rename result table"处发生了等待,在我们alter一张大表的时候,真正触礁的地方也就这里,这也呼应下面的案例1

案例

案例 1:
5.5下给大表加主键时会锁住读的问题

加锁策略:

1)上MDL读锁
2)操作数据,最耗时,需copy data,简易流程如下:
       a) 创建临时表A,重定义A为修改后的表结构
       b) 从原表读取数据插入到A表
3)将MDL读锁升级为写锁
       c) 删除原表,将A重命名为原表名
4)释放MDL写锁

5.5必须在打开了old_alter_table后才会采取上述策略,但事实上,无论其是否打开
对主键操作都必须copy data,基于此5.6已经改进,从而把MDL写锁仅圈在重命名的操作
这样阻塞时间非常之短,几乎可以认为不阻塞读,在5.5时做alter建议将其打开,减小持锁时间

案例 2:
mysqldump备份不一致导致备库延迟问题

由于5.1中没有引入MDL,所有在mysqldump备份过程中,并发DDL都会对其产生影响,导致备份集不一致
最终表现是使用此备份集恢复的备库在relay主库binlog会出现slave error,造成在备库上的读与主库数据不一致
因为5.5有了MDL,所以–single-transaction时,事务内操作过的表都会持有MDL,因此不会被DDL破坏
例如,mysqldump已经备份了a,b,c表,因为它们在事务内,事务还没提交,它们的MDL不会释放
因此另外的线程如果做a,b,c中任意一张表的DDL操作,都会出现Waiting for table metadata lock,而还没备份到的表不会持有MDL,因此还可以做DDL

监控

lock_wait_timeout

  1. mysql> show variables like \'lock_wait_timeout\';
  2. +-------------------+----------+
  3. | Variable_name | Value |
  4. +-------------------+----------+
  5. | lock_wait_timeout | 31536000 |
  6. +-------------------+----------+
  7. 1 row in set (0.00 sec)

官方文档解释:

  1. This variable specifies the timeout in seconds for attempts to acquire metadata locks.
  2. The permissible values range from 1 to 31536000 (1 year). The default is 31536000

5.5默认是1年!太长了,个人认为不是很合理,这个需要我们DBA自己调整,50秒会比较好
另外,这个既然针对MDL的超时设置,那为何不命名为"metadata_lock_wait_timeout"呢?

show open tables:当前打开的表,与flush tables结合,能判断表的活性

show status like \'Open%tables\';是上面的简化输出

诊断

对于MyISAM表:

当发现Query由于“Waiting for table metadata lock”而挂起等待MyISAM表,这是因为该表已经被包含在一个未提交的事务,无论其是否有意
而且常见的锁追踪技巧此时也没有作用,比如InnoDB Lock Monitor, InnoDB Status, mysqladmin debug output, INFORMATION_SCHEMA等
事实上,在select一张表之前先SET AUTOCOMMIT=0,那么之后,直到该事务提交或结束后其它DDL才能够得到该表的控制权

测试:

  1. Connection #1:
  2.  
  3. create table t1 (id int) engine=myisam;
  4. set @@autocommit=0;
  5. select * from t1;
  6. Connection #2:
  7.  
  8. alter table t1 rename to t2; <-- Hangs

对于InnoDB表:

倘若在InnoDB表遇见此问题,我们能够用SHOW ENGINE INNODB STATUS,优先查看transactions部分

测试:

  1. create table t3 (id int) engine=innodb;
  2. create table t4 (id int) engine=innodb;
  3. delimiter |
  4. CREATE TRIGGER t3_trigger AFTER INSERT ON t3
  5. FOR EACH ROW BEGIN
  6. INSERT INTO t4 SET id = NEW.id;
  7. END;
  8. |
  9. delimiter ;
  1. Connection #1:
  2.  
  3. begin;
  4. insert into t3 values (1);
  1. Connection #2:
  2.  
  3. drop trigger if exists t3_trigger; <-- Hangs
  4.  
  5. mysql> SHOW ENGINE INNODB STATUS\G;
  6. ....
  7. ....
  8. ....
  9. ------------
  10. TRANSACTIONS
  11. ------------
  12. Trx id counter BF03
  13. Purge done for trx\'s n:o < BD03 undo n:o < 0
  14. History list length 82
  15. LIST OF TRANSACTIONS FOR EACH SESSION:
  16. ---TRANSACTION 0, not started
  17. MySQL thread id 4, OS thread handle 0xa7d3fb90, query id 40 localhost root
  18. show engine innodb status
  19. ---TRANSACTION BF02, ACTIVE 38 sec
  20. 2 lock struct(s), heap size 320, 0 row lock(s), undo log entries 2
  21. MySQL thread id 2, OS thread handle 0xa7da1b90, query id 37 localhost root
  22.  
  23. ...
  24. ...
  25. ...

官方文档对 TRANSACTIONS解释:

  1. TRANSACTIONS
  2.  
  3. If this section reports lock waits, your applications might have lock contention.
  4. The output can also help to trace the reasons for transaction deadlocks.

除此之外,我们还可以利用information_schema,以下是几个有用SQL:
① 锁等待

  1. SELECT * FROM INNODB_LOCK_WAITS

② 被阻塞的事务

  1. SELECT *
  2. FROM INNODB_LOCKS
  3. WHERE LOCK_TRX_ID IN (SELECT BLOCKING_TRX_ID FROM INNODB_LOCK_WAITS)

或者

  1. SELECT INNODB_LOCKS.*
  2. FROM INNODB_LOCKS
  3. JOIN INNODB_LOCK_WAITS
  4. ON (INNODB_LOCKS.LOCK_TRX_ID = INNODB_LOCK_WAITS.BLOCKING_TRX_ID)

③ 指定表上的锁

  1. SELECT * FROM INNODB_LOCKS
  2. WHERE LOCK_TABLE = db_name.table_name

④ 事务与锁

  1. SELECT TRX_ID, TRX_REQUESTED_LOCK_ID, TRX_MYSQL_THREAD_ID, TRX_QUERY
  2. FROM INNODB_TRX
  3. WHERE TRX_STATE = \'LOCK WAIT\'

当然,最好的办法还是综合运用前面2种

与table cache的关系

先明白table cache所解决的问题域:fd(文件描述符)打开/关闭太过频繁导致资源消耗
那么它是如何解决的?tc通过cache所有打开的fd,当有新的连接请求时不需重新打开,结束后也不用关闭

DDL操作终究会被阻塞,即使table cache含有其所需的fd。MySQL认为,tc旧条目必须失效
访问该表的DDL操作必须重新打开fd,下面是个测试

  1. 会话1
  2.  
  3. mysql> show status like \'Open%tables\';
  4. +---------------+-------+
  5. | Variable_name | Value |
  6. +---------------+-------+
  7. | Open_tables | 26 | <==当前打开的表数量
  8. | Opened_tables | 2 | <==已经打开的表数量
  9. +---------------+-------+
  10. 2 rows in set (0.00 sec)
  11.  
  12. 会话2
  13.  
  14. mysql> alter table t add column Oxx char(20) default \'ORACLE\';
  15. Query OK, 3 rows affected (0.05 sec)
  16. Records: 3 Duplicates: 0 Warnings: 0
  17.  
  18. 会话1
  19.  
  20. mysql> select * from t order by id;
  21. +----+------+--------+--------+---------+---------+-------+--------+--------+--------+--------+
  22. | id | name | cc | dd | EE | ff | OO | OE | OF | OX | Oxx |
  23. +----+------+--------+--------+---------+---------+-------+--------+--------+--------+--------+
  24. | 1 | a | c lang | Elang | Golang | Golang | MySQL | ORACLE | ORACLE | ORACLE | ORACLE |
  25. | 2 | e | c lang | Elang | Golang | Golang | MySQL | ORACLE | ORACLE | ORACLE | ORACLE |
  26. | 3 | c | c lang | Elang | Golang | Golang | MySQL | ORACLE | ORACLE | ORACLE | ORACLE |
  27. +----+------+--------+--------+---------+---------+-------+--------+--------+--------+--------+
  28. 3 rows in set (0.00 sec)
  29.  
  30. mysql> show status like \'Open%tables\';
  31. +---------------+-------+
  32. | Variable_name | Value |
  33. +---------------+-------+
  34. | Open_tables | 27 |
  35. | Opened_tables | 3 |
  36. +---------------+-------+
  37. 2 rows in set (0.00 sec)
  38.  
  39. 会话2
  40.  
  41. mysql> alter table t add column Oxf char(20) default \'ORACLE\';
  42. Query OK, 3 rows affected (0.06 sec)
  43. Records: 3 Duplicates: 0 Warnings: 0
  44.  
  45. 会话1
  46.  
  47. mysql> show status like \'Open%tables\';
  48. +---------------+-------+
  49. | Variable_name | Value |
  50. +---------------+-------+
  51. | Open_tables | 26 |
  52. | Opened_tables | 3 |
  53. +---------------+-------+
  54. 2 rows in set (0.00 sec)

结论:

当需要对"热表"做DDL,需要特别谨慎,否则,容易造成MDL等待,导致连接耗尽或者拖垮Server

参考资料:

http://dinglin.iteye.com/blog/1884696
http://www.xaprb.com/blog/2012/08/28/debugging-metadata-locking-in-mysql-5-5/
http://blog.chinaunix.net/uid-28212952-id-3400571.html
http://www.chriscalender.com/?p=1189
http://www.mysqlperformanceblog.com/2013/02/01/implications-of-metadata-locking-changes-in-mysql-5-5/

By 迦叶

2013-12-17

Good Luck

Metadata Lock原理3的更多相关文章

  1. Metadata Lock原理6

      一 简介   上一篇文章 <MetaData Lock 之一>  简单的介绍了MySQL 引入MDL 的前因后果,本文深入了解MDL的实现原理和运行机制.二 MDL 的类型  meta ...

  2. Metadata Lock原理5

    [MySQL] 之一2015-09-05 15:46:51 分类: MySQL 一 简介 和MySQL打交道比较多的朋友,肯定遇到过 "Waiting for table metadata ...

  3. Metadata Lock原理4

     http://blog.chinaunix.net/uid-28212952-id-3400571.html    Alibaba  今天发生一个故障,MM复制结构(主备库),备库slave del ...

  4. Metadata Lock原理8

    http://www.kancloud.cn/taobaomysql/monthly/67141 MySQL· 5.7优化·Metadata Lock子系统的优化 背景 引入MDL锁的目的,最初是为了 ...

  5. Metadata Lock原理7

    http://blog.itpub.net/22664653/viewspace-1791744/ 一 简介 通过前面两篇文章的介绍,相信读到这里的各位对MDL 锁已经有了比较深入的了解了,本文将结合 ...

  6. Metadata Lock原理2

    同事说开发机更改一个表结构,加字段,但是一直挂在那里,没反应.一开始以为表测试数据量很大,因为mysql增加表字段会重写表,后来看了下数据量很小,就另外查看过程.原因分析和处理如下:  一.环境  m ...

  7. Metadata Lock原理1

    https://www.percona.com/blog/2013/02/01/implications-of-metadata-locking-changes-in-mysql-5-5/ impli ...

  8. mysql metadata lock(三)

    前言 MDL锁主要用来保护Mysql内部对象的元数据,通过MDL机制保证DDL与DML以及SELECT查询操作的并发.MySQL Meta Lock(一)和MySQL Meta Lock(二)已经讲了 ...

  9. mysql metadata lock(二)

    上一篇<mysql metadata lock(一)>介绍了为什么引入MDL,MDL作用以及MDL锁导致阻塞的几种典型场景,文章的最后还留下了一个小小的疑问.本文将更详细的介绍MDL,主要 ...

随机推荐

  1. linux命令——rm

    rm是常用的命令,该命令的功能为删除一个目录中的一个或多个文件或目录,它也可以将某个目录及其下的所有文件及子目录均删除.对于链接文件,只是删除了链接,原有文件均保持不变. rm是一个危险的命令,使用的 ...

  2. 【SQL server】安装和配置

    (1)SQL sever 版本问题1: SQL sever 2000 .SQL sever 2005.SQL sever 2008 .SQL sever 2008 R2 安装的时候需要注意是SQL s ...

  3. SQL经典笔试题之一

    本题用到下面三个关系表: CARD     借书卡.   CNO 卡号,NAME 姓名,CLASS 班级 BOOKS    图书.     BNO 书号,BNAME 书名,AUTHOR 作者,PRIC ...

  4. Redrain个人维护并使用的DuiLib和UiLib库源码下载地址

    转载请说明原出处:http://blog.csdn.net/zhuhongshu/article/details/40740353,谢谢~~ 我把我自己使用的Duilib库和Uilib库都上传到了Gi ...

  5. js代码大全(里面啥都有)

    事件源对象event.srcElement.tagNameevent.srcElement.type 捕获释放event.srcElement.setCapture();  event.srcElem ...

  6. MorningSale 介绍

    MorningSale是一个WEB端的收集门店销售数据,显示销售数据的简单系统,我相信该系统能够有效的提高销售公司在门店销售数据收集 汇总 分析方面的工作效率. 主要功能介绍如下: 1.查看某个店面 ...

  7. ASP.NET MVC之Html.RenderAction

    WEB窗体模式开发惯了,切入MVC模式,好多东西都不懂,每一步都要查资料. 初步得来的一些知识点体会是: _Layout.cshtml就相当于母版页 然后partical视图(部分视图)就是用户控件. ...

  8. 《Genesis-3D开源游戏引擎完整实例教程-2D射击游戏篇08:弹幕系统》本系列完结

    8.弹幕系统 弹幕系统概述: 弹幕系统的设计体现了射击游戏的基本要素,玩家要在敌人放出的大量子弹(弹幕)的细小空隙间闪避,能在玩家闪躲弹幕的时候给玩家带来快感,接近满屏的子弹,增加了对玩家的视觉冲击力 ...

  9. append some buttons to the standard datagrid pager bar

    <script type="text/javascript">  $(function(){   var pager = $('#dg').datagrid('getP ...

  10. 查看解决Oracle对象锁住的问题

    在编译的一个存储过程的时候,对象可能由于被锁住而处于假的卡死状态,这个时候有的是因为,这个过程正在运行中,所以无法编译: 上次我遇到一次,我很清楚的确定这个过程没有运行,可以我就是无法编译,对象一直被 ...