MDL锁
MetaData Lock即元数据锁,在数据库中元数据即数据字典信息包括db,table,function,procedure,trigger,event等。metadata lock主要为了保证元数据的一致性,用于处理不同线程操作同一数据对象的同步与互斥问题。
锁模式 | 含义 | 对应SQL |
---|---|---|
MDL_INTENTION_EXCLUSIVE | 意向排他锁用于global和commit的加锁。 | GLOBAL对象、SCHEMA对象操作会加此锁 |
MDL_SHARED | 只访问元数据 比如表结构,不访问数据。 | FLUSH TABLES with READ LOCK |
MDL_SHARED_HIGH_PRIO | 用于访问information_scheam表,不涉及数据。 | 仅对MyISAM存储引擎有效 |
MDL_SHARED_READ | 访问表结构并且读表数据 | select … lock in share mode产生该锁(8.0版本以后使用select…for share) |
MDL_SHARED_WRITE | 访问表结构并且写表数据 | 一般执行DML的更新语句 或 select … for update产生该锁 |
MDL_SHARED_WRITE_LOW_PRIO | 仅对MyISAM存储引擎有效 | |
MDL_SHARED_UPGRADABLE | 为了online ddl才引入的,特点是允许DML,防止DDL; | ALTER TABLE,一般在执行DDL时在on-line情况下会产生该锁 |
MDL_SHARED_READ_ONLY | 执行lock tables xxx read产生该锁 | |
MDL_SHARED_NO_WRITE | 可升级锁,访问表结构并且读写表数据,并且禁止其它事务写。 | FLUSH TABLES xxx,yyy,zzz READ |
MDL_SHARED_NO_READ_WRITE | 可升级锁,访问表结构并且读写表数据,并且禁止其它事务读写。 | FLUSH TABLE xxx WRITE,lock tables xxx write产生该锁 |
MDL_EXCLUSIVE | 防止其他线程读写元数据 | ALTER TABLE xxx PARTITION BY …,一般在执行DDL时会产生该锁 |
MDL锁的性能与并发改进
手动加锁
手动添加读写锁
事务1 | 事务2 |
---|---|
lock table sbtest1 write;(添加读写锁) | |
select * from sbtest1 limit 1;(阻塞) | |
exit(释放锁) | |
select * from sbtest1 limit 1;(通过) |
如何查看锁信息:
-- 据show open tables可以快速判断哪些表产生了共享读的表锁,以此为基础,可以判断其它的一些相关的锁数据库故障
mysql> show OPEN TABLES where In_use > 0;
+----------+---------+--------+-------------+
| Database | Table | In_use | Name_locked |
+----------+---------+--------+-------------+
| sbtest1 | sbtest1 | 1 | 0 |
+----------+---------+--------+-------------+
1 row in set (0.00 sec)
-- mysql5.7还提供metadata_locks表用来快速查看mdl,5.7不默认开启,需手动开启
mysql> UPDATE performance_schema.setup_instruments set enabled='YES' WHERE NAME = 'wait/lock/metadata/sql/mdl';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> SELECT * FROM performance_schema.metadata_locks where OBJECT_NAME like 'sbtest1'\G;
*************************** 1. row ***************************
OBJECT_TYPE: TABLE
OBJECT_SCHEMA: sbtest1
OBJECT_NAME: sbtest1
OBJECT_INSTANCE_BEGIN: 336823392
LOCK_TYPE: SHARED_NO_READ_WRITE -- 不允许读写锁
LOCK_DURATION: TRANSACTION
LOCK_STATUS: GRANTED
SOURCE: sql_parse.cc:6730
OWNER_THREAD_ID: 1431
OWNER_EVENT_ID: 3
1 row in set (0.00 sec)
手动添加写锁
mysql> lock table sbtest1 read;
Query OK, 0 rows affected, 1 warning (0.00 sec)
mysql> SELECT * FROM performance_schema.metadata_locks where OBJECT_NAME like 'sbtest1'\G;
*************************** 1. row ***************************
OBJECT_TYPE: TABLE
OBJECT_SCHEMA: sbtest1
OBJECT_NAME: sbtest1
OBJECT_INSTANCE_BEGIN: 314326768
LOCK_TYPE: SHARED_READ_ONLY -- 只读
LOCK_DURATION: TRANSACTION
LOCK_STATUS: GRANTED
SOURCE: sql_parse.cc:6730
OWNER_THREAD_ID: 1443
OWNER_EVENT_ID: 8
1 row in set (0.00 sec)
表空间迁移中的元数据锁
表空间迁移流程
将a实例的表的数据迁移到b实例上.
1.在目标实例b上创建一个相同的表
2.在目标库b上执行ALTER TABLE t DISCARD TABLESPACE;
3.在源库a上执行FLUSH TABLES t FOR EXPORT;生成.cfg文件
4.将.ibd文件和.cfg文件拷贝到目标实例b
5.在源库a执行unlock tables;
6.在目标库b执行ALTER TABLE t IMPORT TABLESPACE;
加锁实验
事务1 | 事务2 |
---|---|
FLUSH TABLES sbtest1 FOR EXPORT; | |
select * from sbtest1 where id = 1;(通过) | |
select * from sbtest1 where id = 1 for update;(阻塞) | |
unlock tables; | |
select * from sbtest1 where id = 1 for update;(通过) |
元数据锁查看
mysql> show open tables where in_use > 0;
+----------+---------+--------+-------------+
| Database | Table | In_use | Name_locked |
+----------+---------+--------+-------------+
| sbtest2 | sbtest1 | 1 | 0 |
+----------+---------+--------+-------------+
1 row in set (0.00 sec)
mysql> SELECT * FROM performance_schema.metadata_locks where OBJECT_NAME like 'sbtest1'\G;
*************************** 1. row ***************************
OBJECT_TYPE: TABLE
OBJECT_SCHEMA: sbtest2
OBJECT_NAME: sbtest1
OBJECT_INSTANCE_BEGIN: 299096848
LOCK_TYPE: SHARED_NO_WRITE -- 可读不可写
LOCK_DURATION: TRANSACTION
LOCK_STATUS: GRANTED
SOURCE: sql_parse.cc:6730
OWNER_THREAD_ID: 1437
OWNER_EVENT_ID: 12
1 row in set (0.00 sec)
ERROR:
No query specified
DDL中的元数据锁
Offline DDL和Online DDL最重要的区别:DDL执行过程中是否支持对表写操作,该区别是由DDL执行过程中加不同的元数据锁决定的。
元数据锁是server层的锁,主要用于隔离DML和DDL以及DDL之间的干扰,本章节只介绍DDL中的元数据锁、元数据锁之间的关系以及如何借助元数据锁信息定位锁冲突问题。
DDL中的元数据锁
类型 | 含义 | 作用域 |
---|---|---|
MDL_EXCLUSIVE | 排他锁,防止其他线程读写元数据 | Offline & Online DDL |
MDL_SHARED_UPGRADABLE | 允许读表数据,允许写表数据,禁止DDL | Offline & Online DDL |
MDL_SHARED_NO_WRITE | 允许读表数据,禁止写表数据,禁止DDL | Offline DDL |
MDL_SHARED_READ | 读表数据时加的锁 | DML |
MDL_SHARED_WRITE | 写表数据时加的锁 | DML |
MDL_SHARED_UPGRADABLE介绍
元数据锁之间的关系
1) .MDL_EXCLUSIVE和MDL_SHARED_READ互斥
事务1拥有表的MDL_EXCLUSIVE锁,事务2申请MDL_SHARED_READ锁时等待
事务1拥有表的MDL_SHARED_READ锁,事务2申请MDL_EXCLUSIVE锁时等待
2) MDL_EXCLUSIVE和MDL_SHARED_WRITE互斥
3) MDL_SHARED_UPGRADABLE和MDL_SHARED_UPGRADABLE互斥
4) MDL_SHARED_UPGRADABLEE和MDL_SHARED_READ兼容
5) MDL_SHARED_UPGRADABLEE和MDL_SHARED_WRITE兼容
6) MDL_SHARED_NO_WRITE和MDL_SHARED_READ兼容
7) MDL_SHARED_NO_WRITE和MDL_SHARED_WRITE互斥
Online DDL流程
Online DDL主要包括3个阶段,prepare阶段,ddl执行阶段,commit阶段。下面将主要介绍ddl执行过程中3个阶段的流程。
Prepare阶段
- 创建新的临时frm文件
- 申请MDL_EXCLUSIVE锁,禁止DML读写
- 根据alter类型,确定执行方式(rebuild / no-rebuild)
- 分配row_log对象记录增量(仅rebuild类型需要)
- 生成新的临时ibd文件(仅rebuild类型需要)
执行阶段
- 降级EXCLUSIVE锁为SHARED_UPGRADABLE,允许DML读写
- 扫描old_table的聚集索引每一条记录rec
- 遍历新表的聚集索引和二级索引,逐一处理
- 根据rec构造对应的索引项
- 将构造索引项插入sort_buffer块排序
- 将sort_buffer块更新到新的索引上
- 记录ddl执行过程中产生的增量(仅rebuild类型需要)
- 重放row_log中的操作到新索引上(no-rebuild数据是在原表上更新的)
- 重放row_log间产生dml操作append到row_log最后一个Block
Commit阶段
- 当前Block为row_log最后一个时,禁止读写,升级到MDL_EXCLUSIVE锁
- 重做row_log中最后一部分增量
- 更新innodb的数据字典表
- 提交事务(刷事务的redo日志)
- 修改统计信息
- rename临时idb文件,frm文件
- 变更完成
锁相关
- 执行阶段加的锁是SHARED_UPGRADABLE,该阶段允许并行读写;
- Prepare和Commit阶段,加的锁是EXCLUSIVE,这两个阶段不能并行DML;由此可见,Online DDL并不是全过程允许DML并行。但是Prepare和Commit阶段的耗时很短,占整个DDL流程比例非常小,对业务影响可以忽略不计。反过来,正在执行的业务可能会对DDL产生影响,可能会产生锁冲突的情况
- MDL_SHARED_UPGRADABLE之间是互斥的,所以可以保证同一张表不会并行执行多个DDL。
执行方式
根据DDL是否需要重建表空间,可以分为no-rebuild和rebuild两种方式。
no-rebuild
no-rebuild不涉及表的重建(例如修改字段名),只修改元数据项(添加索引,会产生部分二级索引的写入操作),即只在原表路径下产生.frm文件,是代价最小、速度最快的DDL类型。
rebuild
rebuild涉及表的重建(例如新增字段),在原表路径下创建新的.frm和.ibd文件,拷贝ibd文件时消耗的IO较多。
DDL执行过程中,并行的DML操作原表,同时会申请row log空间记录DML操作,这部分操作会在DDL执行和提交阶段应用到新的表空间中。row log空间是一个独立的空间,其大小可通过参innodb_online_alter_log_max_size控制(默认128M),当DDL过程中,并行的DML超过innodb_online_alter_log_max_size容量,就会报错。
rebuild方式的DDL,对空间有要求,对IO消耗比较大,是代价比较大的DDL类型。
附:在线DDL分类
类型 | 操作 | 是否Inplace | 是否重建表 | 是否允许DML | 是否只修改元数据 |
---|---|---|---|---|---|
index | 创建或添加二级索引 | 是 | 否 | 是 | 否 |
删除索引 | 是 | 否 | 是 | 是 | |
重命名索引 | 是 | 否 | 是 | 是 | |
添加FULLTEXT索引 | 是* | 否* | 否 | 否 | |
添加SPATIAL索引 | 是* | 否* | 否 | 否 | |
更改索引类型 | 是 | 否 | 是 | 是 | |
primary key | 添加主键 | 是 | 是 | 是 | 否 |
删除主键 | 否 | 是 | 否 | 否 | |
同时删除主键并添加 | 是 | 是 | 是 | 否 | |
COLUMN | 添加列 | 是 | 是 | 是* | 否 |
删除列 | 是 | 是 | 是 | 否 | |
重命名列 | 是 | 否 | 是 | 是 | |
重新排序列 | 是 | 是 | 是 | 否 | |
设置/删除列默认值 | 是 | 否 | 是 | 是 | |
更改列数据类型 | 否 | 是 | 否 | 否 | |
扩展VARCHAR列大小 | 是 | 否 | 是 | 是 | |
更改自动增量值 | 是 | 否 | 是 | 否* | |
添加列NULL/NOT NULL | 是 | 是 | 是 | 否 | |
修改ENUM或SET列的定义 | 是 | 否 | 是 | 是 | |
foreign key | 添加外键约束 | 是 | 否 | 是 | 是 |
删除外键约束 | 是 | 否 | 是 | 是 | |
table | 修改ROW_FORMAT | 是 | 是 | 是 | 否 |
修改KEY_BLOCK_SIZE | 是 | 是 | 是 | 否 | |
设置持久表统计信息 | 是 | 否 | 是 | 是 | |
指定字符集 | 是 | 是 | 否 | 否 | |
转换字符集 | 否 | 是 | 否 | 否 | |
OPTIMIZE优化表 | 是* | 是 | 是 | 否 | |
执行空重建表 | 是* | 是 | 是 | 否 | |
重命名表 | 是 | 否 | 是 | 是 | |
table space | 启用或禁用单表文件表空间加密 | 否 | 是 | 否 | 否 |
PARTITION | PARTITION BY | 否 | 不涉及 | 否 | 不涉及 |
ADD PARTITION | 否 | 不涉及 | 否 | 不涉及 | |
DROP PARTITION | 否 | 不涉及 | 否 | 不涉及 | |
DISCARD PARTITION | 否 | 不涉及 | 否 | 不涉及 | |
IMPORT PARTITION | 否 | 不涉及 | 否 | 不涉及 | |
TRUNCATE PARTITION | 是 | 不涉及 | 是 | 不涉及 | |
COALESCE PARTITION | 否 | 不涉及 | 否 | 不涉及 | |
REORGANIZE PARTITION | 否 | 不涉及 | 否 | 不涉及 | |
EXCHANGE PARTITION | 是 | 不涉及 | 是 | 不涉及 | |
ANALYZE PARTITION | 是 | 不涉及 | 是 | 不涉及 | |
CHECK PARTITION | 是 | 不涉及 | 是 | 不涉及 | |
OPTIMIZE PARTITION | 否 | 不涉及 | 否 | 不涉及 | |
REBUILD PARTITION | 否 | 不涉及 | 否 | 不涉及 | |
REPAIR PARTITION | 是 | 不涉及 | 是 | 不涉及 | |
REMOVE PARTITION | 否 | 不涉及 | 否 | 不涉及 |