如何处理 DDL 异常
DDL 原理简介
DRDS 的 DDL 指令会在所有分表上执行对应的 DDL 操作。失败的情况可以分为两类:
- DDL 在分库执行失败。DDL 在任意分库执行出错都可能导致各分表结构不一致。
- 执行长时间无响应。在对大表执行 DDL 操作时,有可能由于分库的执行时间过长导致 DDL 长时间无响应。
分库执行报错的原因多种多样,如建表时表已存在、加列时列已存在等各类冲突、磁盘空间不足等。
长时间无响应一般是由分库的执行时间过长导致的。以 MySQL(RDS 原理与之类似) 为例,DDL 的耗时大部分取决于该操作是 In-Place(直接在源表修改)还是 Copy Table(拷贝表数据)。In-Place 只需要修改元数据就可以了,而 Copy Table 需要重建整张表数据,此外还涉及日志和 buffer 操作。
各类操作与这两项因素的关系详见 MySQL 官方文档 Summary of Online Status for DDL Operations。
判断 DDL 操作是 In-place 还是 Copy Table 操作,可以查看操作结束后 “rows affected” 这一项的返回值。
示例:
改变某列的默认值(非常快,完全不会影响表数据):
Query OK, 0 rows affected (0.07 sec)
增加一个索引(需要花些时间 ,但是 0 rows affected 说明表数据没有被复制):
Query OK, 0 rows affected (21.42 sec)
改变某列的数据类型(耗费大量时间并且需要重建表中的所有数据行):
Query OK, 1671168 rows affected (1 min 35.54 sec)
因此,执行一个大表 DDL 操作前,可以先通过以下步骤判定这是一个快速或慢速操作:
- 复制表结构生成一张克隆表。
- 插入一些数据。
- 在克隆表上执行这个 DDL 操作。
- 检查操作完成后 “rows affected” 值是否是0。一个非0的值意味着该操作需要重建整张表,这时可能需要考虑在流量低谷去执行该操作。
失败处理
DRDS DDL 操作会将所有 SQL 分发到所有分库上并行执行。任一分库上执行失败不会影响其他分库。另外,DRDS 还提供了 CHECK TABLE 指令来检测分表结构的一致性。因此,失败的 DDL 操作可以重新执行,已经执行成功的分库上失败报错并不会影响其他分库。只要保证最终所有分表结构一致即可。
DDL 失败处理步骤
- 使用 CHECK TABLE 指令检查表结构。如果返回结果只有一行且为状态正常则可认为表状态一致。此时进行步骤2,否则进行步骤3。
- 使用 SHOW CREATE TABLE 指令检查表结构。如果显示的表结构符合 DDL 执行后的预期则可认为 DDL 执行成功,否则继续进行步骤3。
- 使用 SHOW PROCESSLIST 指令观察所有当前执行的 SQL 状态。如有仍在执行的 DDL 操作,请等候其执行完成后再进行步骤1、2,检查表结构是否符合预期,否则进行步骤4。
- 在 DRDS 上重新执行 DDL 操作。如果出现 Lock conflict 的报错请进行步骤5,否则进行步骤3。
- 使用 RELEASE DBLOCK 指令释放 DDL 操作锁,然后进行步骤4。
详细操作如下:
检查表结构一致性
使用 CHECK TABLE 指令检查表结构,当返回结果只有一行且显示状态 OK 时,表明表结构一致。
注意:如果在 DMS 上执行 CHECK TABLE 没有返回结果,请在命令行下重试。
mysql> check table `xxxx`; +----------------------------+-------+----------+----------+ | TABLE | OP | MSG_TYPE | MSG_TEXT | +----------------------------+-------+----------+----------+ | TDDL5_APP.xxxx | check | status | OK | +----------------------------+-------+----------+----------+ 1 row in set (0.05 sec)
检查表结构
使用 SHOW CREATE TABLE 指令检查表结构,如果表结构一致且表结构无误时,可认为 DDL 已执行成功。
mysql> show create table `xxxx`; +---------+------------------------------------------------------------------------------------------------------------------+ | Table | Create Table | +---------+------------------------------------------------------------------------------------------------------------------+ | xxxx | CREATE TABLE `xxxx` ( `id` int(11) NOT NULL DEFAULT '0', `NAME` varchar(1024) NOT NULL DEFAULT '', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 dbpartition by hash(`id`) tbpartition by hash(`id`) tbpartitions 3 | +---------+------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.05 sec)
观察当前正在执行的 SQL 语句
有些 DDL 执行速度过慢,发现 DDL 长时间无响应后,可执行 SHOW PROCESSLIST 指令观察所有当前执行的 SQL 状态。
mysql> SHOW PROCESSLIST WHERE COMMAND != 'Sleep'; +---------------+-----------+--------------------+-------------+---------+-----------------------------------------------------------------------+------------------------------------------------------------------------------------------------------+-----------+---------------+-----------+ | ID | USER | DB | COMMAND | TIME | STATE | INFO | ROWS_SENT | ROWS_EXAMINED | ROWS_READ | +---------------+-----------+--------------------+-------------+---------+-----------------------------------------------------------------------+------------------------------------------------------------------------------------------------------+-----------+---------------+-----------+ | 0-0-352724126 | ifisibhk0 | test_123_wvvp_0000 | Query | 15 | Sending data | /*DRDS /42.120.74.88/ac47e5a72801000/ */select `t_item`.`detail_url`,SUM(`t_item`.`price`) from `t_i | NULL | NULL | NULL | | 0-0-352864311 | cowxhthg0 | NULL | Binlog Dump | 13 | Master has sent all binlog to slave; waiting for binlog to be updated | NULL | NULL | NULL | NULL | | 0-0-402714566 | ifisibhk0 | test_123_wvvp_0005 | Query | 14 | Sending data | /*DRDS /42.120.74.88/ac47e5a72801000/ */select `t_item`.`detail_url`,`t_item`.`price` from `t_i | NULL | NULL | NULL | | 0-0-402714795 | ifisibhk0 | test_123_wvvp_0005 | Alter | 114 | Sending data | /*DRDS /42.120.74.88/ac47e5a72801000/ */ALTER TABLE `Persons` ADD `Birthday` date | NULL | NULL | NULL | ...... +---------------+-----------+--------------------+-------------+---------+-----------------------------------------------------------------------+------------------------------------------------------------------------------------------------------+-----------+---------------+-----------+ 12 rows in set (0.03 sec)
TIME 列代表该指令已经执行的秒数。发现过慢指令后,例如图中,可使用 KILL '0-0-402714795' 指令来取消慢指令。
注意: DRDS 中一个逻辑 SQL 对应多条分库指令,因此为了停止一个逻辑 DDL 可能需要 Kill 多条指令。从 SHOW PROCESSLIST 结果集的 INFO 列判断该指令归属的逻辑 SQL。
Lock conflict 报错处理
DRDS 执行 DDL 操作先会加库级锁,操作完后再释放掉。KILL DDL 操作很可能会导致该锁没有释放,此时再执行 DDL 会有以下报错:
Lock conflict , maybe last DDL is still running
此时执行 RELEASE DBLOCK 释放该锁即可。指令取消及锁释放后,选择业务低谷甚至停止期间,重新执行该 DDL。
其它问题
DMS 或其它客户端无法显示修改后的表结构
为了兼容某些客户端从系统表(如 COLUMNS 或 TABLES)中获取表结构的功能,DRDS 在用户0分库 RDS 里建立了一个影子库,影子库名与用户的 DRDS 逻辑库名一致。存储了所有用户库里的表结构等信息。
DMS 获取 DRDS 表结构是从影子库系统表中获取的。在处理异常 DDL 过程中,由于种种原因可能会出现用户库表结构正常修改,但是影子库中的表结构却未修改的现象。此时需要用户连接到影子库,在该库中重新对表进行一次 DDL 操作即可。
注意: CHECK TABLE 不会检测影子库表结构与用户库是否一致。
版权声明
本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。
评论