MySQL 5.6中如何定位DDL被阻塞的问题

本文介绍了一种针对MySQL5.6版本中DDL被阻塞问题的定位方法,通过对活跃事务的追踪和SQL语句的分析,有效地识别出造成阻塞的原因。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在上一篇文章《MySQL 5.7中如何定位DDL被阻塞的问题》中,对于DDL被阻塞问题的定位,我们主要是基于MySQL 5.7新引入的performance_schema.metadata_locks表。提出的定位方法,颇有种"锦上添花"的意味,而且,也只适用于MySQL 5.7开始的版本。

但在实际生产中,MySQL 5.6还是占绝不多数。虽然MySQL 8.0都已经GA了,但鉴于数据库的特殊性,在对待升级的这个事情上,相当一部分人还是秉持着一种“不主动”的态度。

既然MySQL 5.6用者众多,有没有一种方法,来解决MySQL 5.6的这个痛点呢?

 

还是之前的测试Demo

会话1开启了事务并执行了三个操作,但未提交,此时,会话2执行了alter table操作,被阻塞。

session1> begin;
Query OK, 0 rows affected (0.00 sec)

session1> delete from slowtech.t1 where id=2;
Query OK, 1 row affected (0.00 sec)

session1> select * from slowtech.t1;
+------+------+
| id   | name |
+------+------+
|    1 | a    |
+------+------+
row in set (0.00 sec)

session1> update slowtech.t1 set name='c' where id=1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

session2> alter table slowtech.t1 add c1 int; ##被阻塞

session3> show processlist;
+----+------+-----------+------+---------+------+---------------------------------+------------------------------------+
| Id | User | Host      | db   | Command | Time | State                           | Info                               |
+----+------+-----------+------+---------+------+---------------------------------+------------------------------------+
|  2 | root | localhost | NULL | Sleep   |   51 |                                 | NULL                               |
|  3 | root | localhost | NULL | Query   |    0 | starting                        | show processlist                   |
|  4 | root | localhost | NULL | Query   |    9 | Waiting for table metadata lock | alter table slowtech.t1 add c1 int |
+----+------+-----------+------+---------+------+---------------------------------+------------------------------------+
rows in set (0.00 sec)

 

其实,导致DDL阻塞的操作,无非两类: 

1. 慢查询  

2. 表上有事务未提交

其中,第一类比较好定位,通过show processlist即能发现。而第二类基本没法定位,因为未提交事务的连接在show processlist中的输出同空闲连接一样。

如下面Id为2的连接,虽然Command显示为“Sleep”,其实是事务未提交。

mysql> show processlist;
+----+------+-----------+------+---------+------+---------------------------------+------------------------------------+
| Id | User | Host      | db   | Command | Time | State                           | Info                               |
+----+------+-----------+------+---------+------+---------------------------------+------------------------------------+
|  2 | root | localhost | NULL | Sleep   |   77 |                                 | NULL                               |
|  3 | root | localhost | NULL | Query   |    0 | starting                        | show processlist                   |
|  4 | root | localhost | NULL | Query   |   44 | Waiting for table metadata lock | alter table slowtech.t1 add c1 int |
+----+------+-----------+------+---------+------+---------------------------------+------------------------------------+
3 rows in set (0.00 sec)

 

所以,网上有kill空闲(Command为Sleep)连接的说法,其实也不无道理,但这样做就太简单粗暴了,难免会误杀。

其实,既然是事务,在information_schema. innodb_trx中肯定会有记录,如会话1中的事务,在表中的记录如下,

mysql> select * from information_schema.innodb_trx\G
*************************** 1. row ***************************
                    trx_id: 1050390
                 trx_state: RUNNING
               trx_started: 2018-07-17 08:55:32
     trx_requested_lock_id: NULL
          trx_wait_started: NULL
                trx_weight: 4
       trx_mysql_thread_id: 2
                 trx_query: NULL
       trx_operation_state: NULL
         trx_tables_in_use: 0
         trx_tables_locked: 1
          trx_lock_structs: 2
     trx_lock_memory_bytes: 1136
           trx_rows_locked: 3
         trx_rows_modified: 2
   trx_concurrency_tickets: 0
       trx_isolation_level: REPEATABLE READ
         trx_unique_checks: 1
    trx_foreign_key_checks: 1
trx_last_foreign_key_error: NULL
 trx_adaptive_hash_latched: 0
 trx_adaptive_hash_timeout: 0
          trx_is_read_only: 0
trx_autocommit_non_locking: 0
1 row in set (0.00 sec)

 

其中trx_mysql_thread_id是线程id,结合performance_schema.threads,可以知道当前哪些连接上存在着活跃事务,这样就进一步缩小了可被kill的线程范围。

 但从影响程度上,和kill所有Command为Sleep的连接没太大区别,毕竟,kill真正的空闲连接对业务的影响不大。

 此时,依然可以借助performance_schema. events_statements_history表。

 在上篇MySQL 5.7的分析中,我们是首先知道引发阻塞的线程ID,然后利用events_statements_history表,查看该线程的相关SQL。

 而在MySQL 5.6中,我们并不知道引发阻塞的线程ID,但是,我们可以反其道而行之,利用穷举法,首先统计出所有线程在当前事务执行过的所有SQL,然后再判断这些SQL中是否包含目标表。

 

具体SQL如下,

SELECT
    processlist_id,
    sql_text 
FROM
    (
    SELECT
        c.processlist_id,
        substring_index( sql_text, "transaction_begin;",-1 ) sql_text 
    FROM
        information_schema.innodb_trx a,
        (
        SELECT
            thread_id,
            group_concat( CASE WHEN EVENT_NAME = 'statement/sql/begin' THEN "transaction_begin" ELSE sql_text END ORDER BY event_id SEPARATOR ";" ) AS sql_text 
        FROM
            performance_schema.events_statements_history 
        GROUP BY
            thread_id 
        ) b,
        performance_schema.threads c 
    WHERE
        a.trx_mysql_thread_id = c.processlist_id 
        AND b.thread_id = c.thread_id 
    ) t 
WHERE
    sql_text LIKE '%t1%';

+----------------+---------------------------------------------------------------------------------------------------------+
| processlist_id | sql_text                                                                                                |
+----------------+---------------------------------------------------------------------------------------------------------+
|              2 | delete from slowtech.t1 where id=2;select * from slowtech.t1;update slowtech.t1 set name='c' where id=1 |
+----------------+---------------------------------------------------------------------------------------------------------+
1 row in set (0.01 sec)

从输出来看,确实也达到了预期效果。

 

需要注意的是,在MySQL5.6中,events_statements_history默认是没有开启的。

mysql> SELECT * FROM performance_schema.setup_consumers WHERE NAME LIKE '%statements%';
+--------------------------------+---------+
| NAME                           | ENABLED |
+--------------------------------+---------+
| events_statements_current      | YES     |
| events_statements_history      | NO      |
| events_statements_history_long | NO      |
| statements_digest              | YES     |
+--------------------------------+---------+
4 rows in set (0.00 sec)

 

<think>我们正在讨论如何在创建数据库索引时避免表被锁定。根据引用[1]和引用[2],我们可以知道不同数据库有不同的锁定机制和索引创建方法。 用户的问题是:创建数据库索引时如何避免表被锁定? 分析: 1.MySQL中,创建索引时默认可能会锁定表(尤其是MyISAM引擎),但InnoDB引擎支持在线DDL,可以在一定程度上避免完全锁定表。 2. 在PostgreSQL中,可以使用CONCURRENTLY选项来创建索引,避免写锁定。 因此,我们需要分数据库类型来讨论。 步骤: 1. 对于MySQL(特别是InnoDB引擎): - 使用`ALGORITHM=INPLACE`和`LOCK=NONE`选项可以在创建索引时尽量减少锁定。 - 注意:即使使用在线DDL,在索引创建的某些阶段仍可能会有短暂的元数据锁,但不会阻塞读写操作。 2. 对于PostgreSQL: - 使用`CREATE INDEX CONCURRENTLY`语法,允许在创建索引时不阻塞写操作。 另外,还需要考虑: - 索引创建期间可能会对性能有一定影响,因为数据库需要维护索引结构。 - 在大型表上创建索引应选择在业务低峰期进行,即使使用了避免锁定的方法。 详细步骤: 一、MySQL避免锁定的方法 在MySQL中,从5.6版本开始,InnoDB支持在线DDL操作。对于创建索引,可以使用以下语法: ```sql CREATE INDEX index_name ON table_name (column_name) ALGORITHM=INPLACE LOCK=NONE; ``` 解释: - `ALGORITHM=INPLACE`:表示使用“原地”算法,即不需要重建整个表。 - `LOCK=NONE`:表示在索引构建期间不锁定表,允许并发的DML操作。 但是,需要注意: 1. 不是所有的索引创建都可以使用`LOCK=NONE`。如果存储引擎不支持或者索引类型不支持,则可能无法使用。 2. 如果操作不能使用`LOCK=NONE`,那么可以尝试使用`LOCK=SHARED`(允许读取,不允许写入)或`LOCK=DEFAULT`(由系统决定,尽量不锁定)。 二、PostgreSQL避免锁定的方法 在PostgreSQL中,可以使用并发创建索引: ```sql CREATE INDEX CONCURRENTLY index_name ON table_name (column_name); ``` 注意: 1. 使用`CONCURRENTLY`时,索引的创建会分成多个阶段,并且不会阻塞写操作(如INSERT、UPDATE、DELETE)。 2. 但是,创建时间会比普通创建索引长,因为需要额外的处理来保证一致性。 3. 另外,在并发创建索引的过程中,如果出现错误(比如唯一索引有重复值),会留下一个无效的索引(标记为INVALID),需要手动清理。 三、其他注意事项 1. 在创建索引之前,最好对表进行清理(如删除不需要的数据),以减少索引的大小和创建时间。 2. 监控数据库状态:在创建索引期间,观察数据库的负载情况。 3. 备份:在重要表上操作前,建议先备份。 总结: 避免表被锁定的关键在于使用数据库提供的在线索引创建功能。MySQL使用`ALGORITHM=INPLACE LOCK=NONE`,而PostgreSQL使用`CONCURRENTLY`选项。 引用说明: 根据引用[1],MySQL中InnoDB支持行级锁,而创建索引时使用在线DDL可以避免表锁[^1]。 根据引用[2],PostgreSQL可以使用`CONCURRENTLY`选项来避免写锁定[^2]。 最后,生成相关问题: 1. 在线创建索引对数据库性能有什么影响? 2. 如何监控索引创建的进度? 3. 索引创建失败后如何处理? 注意:在回答中,我们引用了[^1]和[^2],在末尾需要标注引用。</think>### 创建数据库索引时避免表锁定的方法 在创建索引时避免表锁定是保障数据库高可用的关键。以下是具体解决方案,按数据库类型分类: --- #### **一、MySQL 避免锁定的方法** 1. **使用在线 DDL 语法** 通过 `ALGORITHM=INPLACE` 和 `LOCK=NONE` 参数实现非阻塞操作: ```sql CREATE INDEX idx_name ON table_name (column) ALGORITHM=INPLACE LOCK=NONE; ``` - **原理**:InnoDB 引擎仅修改索引结构,不重建整表,允许并发 DML 操作(如 INSERT/UPDATE)[^1]。 - **限制**: - 不支持全文索引或空间索引 - 若表包含 `FULLTEXT` 索引,需降级为 `LOCK=SHARED`(允许读,阻塞写) 2. **分区操作** 对超大表按分区逐个创建索引: ```sql ALTER TABLE table_name REBUILD PARTITION p1, p2; -- 仅锁定当前分区 ``` 3. **利用 Percona Toolkit** 第三方工具 `pt-online-schema-change` 通过影子表实现零锁: ```bash pt-online-schema-change --alter "ADD INDEX idx_name(column)" D=database,t=table ``` --- #### **二、PostgreSQL 避免锁定的方法** 1. **并发索引(`CONCURRENTLY`)** ```sql CREATE INDEX CONCURRENTLY idx_name ON table_name (column); ``` - **优势**:允许读写操作持续进行[^2] - **注意事项**: - 耗时比普通索引长 2-3 倍 - 失败时可能遗留无效索引(需手动 `DROP INDEX`) - 需确保事务快照一致性 2. **后台构建索引** 使用 `pg_background` 扩展在后台执行: ```sql SELECT pg_background('CREATE INDEX idx_name ON table_name(column)'); ``` --- #### **三、通用优化策略** 1. **低峰期操作** - 在业务低负载时段(如凌晨)执行 - 监控 `SHOW PROCESSLIST`(MySQL)或 `pg_stat_activity`(PG)确认并发量 2. **事务控制** - 显式启用短事务: ```sql BEGIN; CREATE INDEX ...; -- 快速操作 COMMIT; ``` 3. **资源调配** - 调大内存参数(如 MySQL 的 `innodb_buffer_pool_size`) - 确保磁盘剩余空间 > 索引预估大小的 2 倍 4. **渐进式索引** ```sql /* 分批次创建(示例)*/ INSERT INTO index_temp_table SELECT id FROM main_table WHERE create_time < '2023-01-01'; CREATE INDEX ON index_temp_table(id); -- 先建部分索引 ``` --- #### **四、各引擎锁定对比** | 存储引擎 | 锁定级别 | 无锁方案 | |----------------|----------|------------------------------| | **InnoDB** | 行级锁 | `ALGORITHM=INPLACE LOCK=NONE` | | **MyISAM** | 表级锁 | 不支持(需停机维护)[^1] | | **PostgreSQL** | 行级锁 | `CREATE INDEX CONCURRENTLY` | > ⚠️ **关键原则**: > - 避免在事务中混合 DDL 和 DML 语句 > - 索引字段总长度超过 767 字节时,MySQL 会降级为表锁 > - 监控锁等待:`SHOW ENGINE INNODB STATUS`(MySQL)或 `pg_locks`(PG) --- #### **操作流程图** ```mermaid graph TD A[开始创建索引] --> B{是否支持在线DDL?} B -- MySQL InnoDB --> C[使用 ALGORITHM=INPLACE] B -- PostgreSQL --> D[使用 CONCURRENTLY] B -- MyISAM/其他 --> E[停机窗口操作] C --> F[监控锁状态] D --> F F --> G{是否出现锁等待?} G -- 是 --> H[终止操作并调整资源] G -- 否 --> I[完成索引创建] ``` 通过以上策略,可在 1000 万级数据表上实现业务无感知的索引创建,典型耗时参考: - MySQL(SSD+优化):10-30 分钟 - PostgreSQL(并发模式):20-60 分钟
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值