作者:張洛丹
愛可生 DBA 團隊成員,主要負責 MySQL 故障處理和公司自動化運維平臺維護。對技術執著,為客戶負責。
本文來源:原創投稿
*愛可生開源社區出品,原創內容未經授權不得隨意使用,轉載請聯系小編并注明來源。
Online DDL 工具:pt-osc
對于 MySQL Online DDL 目前主流的有三種工具:
- 原生 Online DDL;
- pt-osc(online-schema-change),
- gh-ost
本文主要講解 pt-online-schema-change 的使用以及三種工具的簡單對比。
一、原理及限制
1.1 原理
1. 創建一個與原表結構相同的空表,表名是 _new 后綴;
2. 修改步驟 1 創建的空表的表結構;
3. 在原表上加三個觸發器:delete/update/insert,用于 copy 數據過程中,將原表中要執行的語句在新表中執行;
4. 將原表數據以數據塊(chunk)的形式 copy 到新表;
5. rename 原表為 old 表,并把新表 rename 為原表名,然后刪除舊表;
6. 刪除觸發器。
1.2 限制
1. 原表上要有 primary key 或 unique index,因為當執行該工具時會創建一個 DELETE 觸發器來更新新表;
注意:一個例外的情況是 --alter 指定的子句中是在原表中的列上創建 primary key 或 unique index,這種情況下將使用這些列用于 DELETE 觸發器。
2. 不能使用 rename 子句來重命名表;
3. 列不能通過刪除 + 添加的方式來重命名,這樣將不會 copy 原有列的數據到新列;
4. 如果要添加的列是 not null,則必須指定默認值,否則會執行失敗;
5. 刪除外鍵約束(DROP FOREIGN KEY constraint_name),外鍵約束名前面必須添加一個下劃線 '_',即需要指定名稱 _constraint_name,而不是原始的 constraint_name;
例如:
CONSTRAINT `fk_foo` FOREIGN KEY (`foo_id`) REFERENCES `bar` (`foo_id`)
必須指定 --alter "DROP FOREIGN KEY _fk_foo"。
二、使用
2.1 語法
pt-online-schema-change [OPTIONS] DSN
其中 DSN 是指 Data Source Name,是連接數據庫的變量信息。格式為 key=value。
DSN 的 key 有:
- A:默認字符集
- D:數據庫
- F:只從給定的文件中讀取默認值
- P:端口號
- S:socket 文件
- h:主機 IP 或主機名
- p:密碼
- t:要更新的表
- u:用戶名
2.2 參數字典(文末)
三、使用
3.1 安裝
-- 安裝 yum 倉庫
yum install https://repo.percona.com/yum/percona-release-latest.noarch.rpm
-- 安裝 percona toolkit
yum install percona-toolkit -y
3.2 使用示例
本示例模擬修改列類型,將列類型由 char(20) 修改為 varchar(200)
版本信息:MySQL 5.7.25,percona-tool 3.2.0
數據量 200 萬
準備
3.2.1 創建用戶
GRANT SELECT, INSERT, UPDATE, DELETE,
CREATE, DROP, PROCESS, REFERENCES,
INDEX, ALTER, SUPER, LOCK TABLES,
REPLICATION SLAVE, TRIGGER
ON *.* TO 'ptosc'@'%'
3.2.2 寫 ALTER 語句
modify c varchar(200) not null default ""
3.2.3 環境檢查
說明:工具在執行時也會進行檢查,如果遇到不能執行的情況,則報錯,建議在執行前先進行 dry-run。
3.2.3.1 檢查要變更的表上是否有主鍵或非空唯一鍵
mysql> desc sbtest1;
+-------+-----------+------+-----+---------+----------------+| Field | Type | Null | Key | Default | Extra |
+-------+-----------+------+-----+---------+----------------+
|
| k | int(11) | NO | MUL | 0 | |
|
| pad | char(60) | NO | | | |
+-------+-----------+------+-----+---------+----------------+
4 rows in set (0.00 sec)
3.2.3.2 檢查是否有其他表外鍵引用該表
select * from information_schema.key_column_usage where referenced_table_schema='testdb' and referenced_table_name='sbtest1'G
*************************** 1. row ***************************
CONSTRAINT_CATALOG: def
CONSTRAINT_SCHEMA: testdb
CONSTRAINT_NAME: test2_ibfk_1
TABLE_CATALOG: def
TABLE_SCHEMA: testdb
TABLE_NAME: test2
COLUMN_NAME: t_id
ORDINAL_POSITION: 1
POSITION_IN_UNIQUE_CONSTRAINT: 1
REFERENCED_TABLE_SCHEMA: testdb
REFERENCED_TABLE_NAME: sbtest1
REFERENCED_COLUMN_NAME: id
1 row in set (0.01 sec)
若有,則需要使用 --alter-foreign-keys-method 選項
3.2.3.3 檢查表上是否有觸發器
mysql> select * from information_schema.triggers where event_object_schema='testdb' and event_object_table='sbtest1'G
*************************** 1. row ***************************
TRIGGER_CATALOG: def
TRIGGER_SCHEMA: testdb
TRIGGER_NAME: trig1
EVENT_MANIPULATION: INSERT
EVENT_OBJECT_CATALOG: def
EVENT_OBJECT_SCHEMA: testdb
EVENT_OBJECT_TABLE: sbtest1
ACTION_ORDER: 1
ACTION_CONDITION: NULL
ACTION_STATEMENT: INSERT INTO time VALUES(NOW())
ACTION_ORIENTATION: ROW
ACTION_TIMING: AFTER
ACTION_REFERENCE_OLD_TABLE: NULL
ACTION_REFERENCE_NEW_TABLE: NULL
ACTION_REFERENCE_OLD_ROW: OLD
ACTION_REFERENCE_NEW_ROW: NEW
CREATED: 2020-08-23 10:43:27.38
SQL_MODE: ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
DEFINER: root@localhost
CHARACTER_SET_CLIENT: utf8
COLLATION_CONNECTION: utf8_general_ci
DATABASE_COLLATION: utf8mb4_bin
1 row in set (0.00 sec)
若有,則需指定 --preserve-triggers 選項,且在 percona tool 3.0.4 起,對于 MySQL 5.7.2 以上,支持原表上有觸發器,建議使用前在測試環境進行測試。
官方 issue 鏈接:https://jira.percona.com/browse/PT-91
3.2.3.4 檢查從庫是否設置 change filter
show slave statusG
... Replicate_Do_DB: Replicate_Ignore_DB: Replicate_Do_Table: Replicate_Ignore_Table: Replicate_Wild_Do_Table: Replicate_Wild_Ignore_Table:
如果設置了 change filter,則不會執行,除非指定 --no-check-replication-filters
3.2.4 執行 dry run
# pt-online-schema-change --print --statistics
--progress time,30 --preserve-triggers --user=ptosc
--password=ptosc --alter 'modify c varchar(200) not null default ""'
h=127.0.1.1,P=3306,D=testdb,t=sbtest1
--pause-file=/tmp/aa.txt --max-load=threads_running=100,threads_connected=200
--critical-load=threads_running=1000 --chunk-size=1000
--alter-foreign-keys-method auto --dry-runOperation, tries, wait: analyze_table, 10, 1
copy_rows, 10, 0.25
create_triggers, 10, 1
drop_triggers, 10, 1
swap_tables, 10, 1
update_foreign_keys, 10, 1
Child tables: `testdb`.`test2` (Approx. 1 rows)
Will automatically choose the method to update foreign keys.Starting a dry run. `testdb`.`sbtest1` will not be altered. Specify --execute instead of --dry-run to alter the table.
Creating new table...
CREATE TABLE `testdb`.`_sbtest1_new` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`k` int(11) NOT NULL DEFAULT '0',
`c` char(120) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
`pad` char(60) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k_1` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=2000001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin
Created new table testdb._sbtest1_new OK.
Altering new table...
ALTER TABLE `testdb`.`_sbtest1_new` modify c varchar(200) not null default ""
Altered `testdb`.`_sbtest1_new` OK.
Not creating triggers because this is a dry run.
Not copying rows because this is a dry run.
INSERT LOW_PRIORITY IGNORE INTO `testdb`.`_sbtest1_new` (`id`, `k`, `c`, `pad`) SELECT `id`, `k`, `c`, `pad` FROM `testdb`.`sbtest1` FORCE INDEX(`PRIMARY`) WHERE ((`id` >= ?)) AND ((`id` <= ?)) LOCK IN SHARE MODE /*pt-online-schema-change 6337 copy nibble*/
SELECT /*!40001 SQL_NO_CACHE */ `id` FROM `testdb`.`sbtest1` FORCE INDEX(`PRIMARY`) WHERE ((`id` >= ?)) ORDER BY `id` LIMIT ?, 2 /*next chunk boundary*/
Not determining the method to update foreign keys because this is a dry run.
2020-08-23T13:24:19 Adding original triggers to new table.
Not swapping tables because this is a dry run.
Not updating foreign key constraints because this is a dry run.
Not dropping old table because this is a dry run.
Not dropping triggers because this is a dry run.
DROP TRIGGER IF EXISTS `testdb`.`pt_osc_testdb_sbtest1_del`
DROP TRIGGER IF EXISTS `testdb`.`pt_osc_testdb_sbtest1_upd`
DROP TRIGGER IF EXISTS `testdb`.`pt_osc_testdb_sbtest1_ins`
2020-08-23T13:24:19 Dropping new table...
DROP TABLE IF EXISTS `testdb`.`_sbtest1_new`;
2020-08-23T13:24:19 Dropped new table OK.
# Event Count# ====== =====# INSERT 0
Dry run complete. `testdb`.`sbtest1` was not altered.
- --print:打印工具執行的 SQL 語句。
- --statistics:打印統計信息。
- --pause-file:當指定的文件存在時,終止執行。
- --max-load:超過指定負載時,暫定執行。
- --critical-load:超過指定負載時,終止執行。
- --chunck-size:指定每次復制的行數。
- --alter-foreign-keys-method:指定外鍵更新方式。
- --progress:copy 進度打印的頻率。
3.2.5 執行
將 --dry-run 修改為 --execute
# pt-online-schema-change --print --statistics
--progress time,30 --preserve-triggers --user=ptosc
--password=ptosc --alter 'modify c varchar(200) not null default ""'
h=127.0.1.1,P=3306,D=testdb,t=sbtest1
--pause-file=/tmp/aa.txt --max-load=threads_running=100,threads_connected=200
--critical-load=threads_running=1000 --chunk-size=1000
--alter-foreign-keys-method auto --executeFound 2 slaves:
10-186-64-51 -> 10.186.64.51:3306
10-186-64-48 -> 10.186.64.48:3306
Will check slave lag on:10-186-64-51 -> 10.186.64.51:3306
10-186-64-48 -> 10.186.64.48:3306
Operation, tries, wait: analyze_table, 10, 1
copy_rows, 10, 0.25
create_triggers, 10, 1
drop_triggers, 10, 1
swap_tables, 10, 1
update_foreign_keys, 10, 1
Child tables: `testdb`.`test2` (approx. 1 rows)
Will automatically choose the method to update foreign keys.Altering `testdb`.`sbtest1`...
Creating new table...
CREATE TABLE `testdb`.`_sbtest1_new` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`k` int(11) NOT NULL DEFAULT '0',
`c` char(120) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
`pad` char(60) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k_1` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=2000001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin
Created new table testdb._sbtest1_new OK.
Altering new table...
ALTER TABLE `testdb`.`_sbtest1_new` modify c varchar(200) not null default ""
Altered `testdb`.`_sbtest1_new` OK.
2020-08-23T14:44:53 Creating triggers...
2020-08-23T14:44:53 Created triggers OK.
2020-08-23T14:44:53 Copying approximately 1972656 rows...
INSERT LOW_PRIORITY IGNORE INTO `testdb`.`_sbtest1_new` (`id`, `k`, `c`, `pad`) SELECT `id`, `k`, `c`, `pad` FROM `testdb`.`sbtest1` FORCE INDEX(`PRIMARY`) WHERE ((`id` >= ?)) AND ((`id` <= ?)) LOCK IN SHARE MODE /*pt-online-schema-change 15822 copy nibble*/
SELECT /*!40001 SQL_NO_CACHE */ `id` FROM `testdb`.`sbtest1` FORCE INDEX(`PRIMARY`) WHERE ((`id` >= ?)) ORDER BY `id` LIMIT ?, 2 /*next chunk boundary*/
Copying `testdb`.`sbtest1`: 52% 00:27 remain
Copying `testdb`.`sbtest1`: 99% 00:00 remain
2020-08-23T14:45:53 Copied rows OK.
2020-08-23T14:45:53 Max rows for the rebuild_constraints method: 4000
Determining the method to update foreign keys...
2020-08-23T14:45:53 `testdb`.`test2`: 1 rows; can use rebuild_constraints
2020-08-23T14:45:53 Adding original triggers to new table.
2020-08-23T14:45:53 Analyzing new table...
2020-08-23T14:45:53 Swapping tables...
RENAME TABLE `testdb`.`sbtest1` TO `testdb`.`_sbtest1_old`, `testdb`.`_sbtest1_new` TO `testdb`.`sbtest1`
2020-08-23T14:45:54 Swapped original and new tables OK.
2020-08-23T14:45:54 Rebuilding foreign key constraints...
ALTER TABLE `testdb`.`test2` DROP FOREIGN KEY `test2_ibfk_1`, ADD CONSTRAINT `_test2_ibfk_1` FOREIGN KEY (`t_id`) REFERENCES `testdb`.`sbtest1` (`id`)
2020-08-23T14:45:54 Rebuilt foreign key constraints OK.
2020-08-23T14:45:54 Dropping old table...
DROP TABLE IF EXISTS `testdb`.`_sbtest1_old`
2020-08-23T14:45:54 Dropped old table `testdb`.`_sbtest1_old` OK.
2020-08-23T14:45:54 Dropping triggers...
DROP TRIGGER IF EXISTS `testdb`.`pt_osc_testdb_sbtest1_del`
DROP TRIGGER IF EXISTS `testdb`.`pt_osc_testdb_sbtest1_upd`
DROP TRIGGER IF EXISTS `testdb`.`pt_osc_testdb_sbtest1_ins`
2020-08-23T14:45:54 Dropped triggers OK.
# Event Count# ================== =====# INSERT 2000
# rebuilt_constraint 1
Successfully altered `testdb`.`sbtest1`.
如上,輸出比較簡單,包括了每一步執行的 SQL。copy 數據期間打印了 copy 的進度以及預計剩余時間;最后打印出統計信息,比如 insert 的數據塊數。
3.2.6 執行后檢查
3.2.6.1 檢查原表是否正確修改
mysql> show create table sbtest1G
*************************** 1. row ***************************
Table: sbtest1
Create Table: CREATE TABLE `sbtest1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`k` int(11) NOT NULL DEFAULT '0',
`c` varchar(200) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
`pad` char(60) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k_1` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=2000001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin
1 row in set (0.00 sec)
mysql> desc sbtest1;+-------+--------------+------+-----+---------+----------------+| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| k | int(11) | NO | MUL | 0 | |
| c | varchar(200) | NO | | | |
| pad | char(60) | NO | | | |
+-------+--------------+------+-----+---------+----------------+
4 rows in set (0.01 sec)
3.2.6.2 檢查引用該表的外鍵
mysql> show create table test2G
*************************** 1. row ***************************
Table: test2Create Table: CREATE TABLE `test2` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`t_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `_test2_ibfk_1` (`t_id`),
CONSTRAINT `_test2_ibfk_1` FOREIGN KEY (`t_id`) REFERENCES `sbtest1` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin1 row in set (0.00 sec)
3.2.6.3 檢查原表上觸發器
mysql> show triggersG
*************************** 1. row ***************************
Trigger: trig1 Event: INSERT Table: sbtest1 Statement: INSERT INTO time VALUES(NOW()) Timing: AFTER Created: 2020-08-23 14:45:53.96 sql_mode: ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
Definer: root@localhostcharacter_set_client: utf8
collation_connection: utf8_general_ci
Database Collation: utf8mb4_bin
1 row in set (0.00 sec)
四、Online DDL 工具對比
4.1 原理對比
這里簡單說一下另外兩個工具:原生 Online DDL 和 gh-ost 的原理。
4.1.1 MySQL 原生 DDL
自 MySQL 5.6 起,MySQL 原生支持 Online DDL,即在執行 DDL 期間允許執行 DML(insert、update、delete)。了解 Online DDL 先了解一下之前 DDL 的 2 種算法 copy 和 inplace。
Copy:
1. 按照原表定義創建一個新的臨時表
2. 對原表加寫鎖(禁止 DML,允許 select)
3. 步驟 1)建立的臨時表執行 DDL
4. 將原表中的數據 copy 到臨時表
5. 釋放原表的寫鎖
6. 將原表刪除,并將臨時表重命名為原表
可見,采用 copy 方式期間需要鎖表,禁止 DML,因此是非 Online 的。比如:刪除主鍵、修改列類型、修改字符集,這些操作會導致行記錄格式發生變化(無法通過全量 + 增量實現 Online)。
Inplace:
在原表上進行更改,不需要生成臨時表,不需要進行數據 copy 的過程。
根據是否行記錄格式,分為兩類:
- rebuild:需要重建表(重新組織聚簇索引)。比如 optimize table、添加索引、添加/刪除列、修改列 NULL/NOT NULL 屬性等;
- no-rebuild:不需要重建表,只需要修改表的元數據,比如刪除索引、修改列名、修改列默認值、修改列自增值等。
對于 rebuild 方式實現 Online 是通過緩存 DDL 期間的 DML,待 DDL 完成之后,將 DML 應用到表上來實現的。例如,執行一個 alter table A engine=InnoDB; 重建表的 DDL 其大致流程如下:
1. 建立一個臨時文件,掃描表 A 主鍵的所有數據頁;
2. 用數據頁中表 A 的記錄生成 B+ 樹,存儲到臨時文件中;
3. 生成臨時文件的過程中,將所有對 A 的操作記錄在一個日志文件(row log)中;
4. 臨時文件生成后,將日志文件中的操作應用到臨時文件,得到一個邏輯數據上與表 A 相同的數據文件;
5. 用臨時文件替換表 A 的數據文件。
說明:
1. 在 copy 數據到新表期間,在原表上是加的 MDL 讀鎖(允許 DML,禁止 DDL)
2. 在應用增量期間對原表加 MDL 寫鎖(禁止 DML 和 DDL)
3. 根據表A重建出來的數據是放在 tmp_file 里的,這個臨時文件是 InnoDB 在內部創建出來的,整個 DDL 過程都在 InnoDB 內部完成。對于 server 層來說,沒有把數據挪動到臨時表,是一個原地操作,這就是“inplace”名稱的來源。
4.1.2 gh-ost
主要原理如下:
1. 創建幽靈表:_xxx_gho(和原表結構一致),_xxx_ghc(用于記錄變更日志)
2. 在步驟 1 中創建的幽靈表上執行 DDL 語句
3. 模擬成備庫連接到真正的主庫或備庫
- 將數據從原表拷貝到幽靈表
- 應用 binlog events 到幽靈表
4. 進行切換(cut-over)
關于 gh-ost 的詳細使用方式可以看看這篇文章《Online DDL工具 gh-ost》
https://mp.weixin.qq.com/s/V3mfuv8EP8UB1fwtfVRHuQ
4.2 如何選擇
從原理中,可以看出幾個關鍵點:
- 可以看到 pt-osc、gh-ost、原生 Online DDL copy 方式(實際上是非 Online),都是需要 copy 原表數據到一個新表,這個是非常耗時的;
- pt-osc 采用觸發器實現應用 DDL 期間的 DML, gh-ost 通過 binlog 應用 DDL 期間的 DML,理論上觸發器會有一定的負載,且 gh-ost 可以從從庫上拉取binlog,對主庫的影響更小;
- 原生 Online DDL 中 Inplace 方式,對于 no-rebuild 方式,不需要重建表,只需要修改表的元數據,這個是非常快的;
- 原生 Online DDL 中 Inplace 方式,對于 rebuild 方式,需要重建表,但是也是在 InnoDB 內部完成的,比 copy 的方式要快;
因此,總結以下幾個選擇工具的判斷依據:
1. 如果 MySQL 版本是 5.6 之前,不支持 Online DDL,選用第三方工具 pt-osc 或 gh-ost;
2. 如果 MySQL 版本是 5.6 以上,對于使用 copy table 方式的 DDL,不支持 Online,使用第三方工具 pt-osc 或 gh-ost;
3. 對于可以使用 Inplace no-rebuild 方式的 DDL,使用原生 Online DDL;
4. 對于使用 Inplace rebuild table 方式的 DDL,如果想使 DDL 過程更加可控,且對從庫延遲比較敏感,使用第三方工具 pt-osc 或 gh-ost,否則使用原生 Online DDL;
5. 對于想減少對主庫的影響,實時交互,可以選用 gh-ost;
pt-osc 參數字典
① 常用基本
--dry-run
- 相當于真正執行前的測試。不會對原表做更改,只會創建和修改新表(不執行創建觸發器、復制數據或替換原始表)
--execute
- 真正執行 DDL
--user, -u
- 用于登錄的用戶名
--password, -p
- 指定密碼,如果密碼中包含逗號,必須使用反斜杠轉義。
--host, -h
- 指定連接的主機。
--port, -P
- 指定端口號。
--socket
- -S,指定用于連接的 socket 文件
--ask-pass
- 不在命令行中指定密碼,連接到 MySQL 時,提示輸入密碼。
--alter “string”
- 指定表結構變更語句。不需要 ALTER TABLE 關鍵字,可以指定多個更改,用逗號隔開。
--database, -D
- 指定數據庫
② 控制輸出形式
- 將工具執行的 SQL 語句打印到 STDOUT,可以和 --dry-run 同時使用。
--progress
- type: array; default: time,30
- 在復制行時,將進度報告打印到 STDERR。該值是一個逗號分隔的列表,由兩部分組成。第一部分可以是 percentage, time, iterations(每秒打印次數);第二部分指定對應的數值,表示打印的頻率。
--quiet, - q
- 表示不要將信息打印到標準輸出(禁用 --progress)。錯誤和警告仍然打印到 STDERR。
--statistics
- 打印統計信息。
③ 表上行為控制
--alter-foreign-keys-method "string"
- 指定修改外鍵以使引用新表。
當該工具重命名原始表以讓新表取而代之時,外鍵跟隨被重命名的表,因此必須更改外鍵以引用新表。
支持兩種方式:rebuild_constraints 和 drop_swap 。
可選值:
auto:
- 自動決定那種方式是最好的。如果可以使用 rebuild_constraints 則使用,否則使用 drop_swap。
rebuild_constraints
此方法使用 ALTER TABLE 刪除并重新添加引用新表的外鍵約束。這是首選的方式,除非子表(引用 DDL 表中列的表)太大,更改會花費太長時間。
通過比較子表的行數和將行從舊表復制到新表的速度來確定是否使用該方式。
- 如果估計可以在比 --chunk-time 更短的時間內修改子表,那么它將使用這種方式。估計修改子表(引用被修改表)所需的時間方法:行復制率乘以 --chunk-size-limit,因為 MySQL alter table 通常比復制行過程快得多。
說明:
由于 MySQL 中的限制,外鍵在更改后不能與之前的名稱相同。該工具在重新定義外鍵時必須重命名外鍵,通常在名稱中添加一個前導下劃線 '_' 。在某些情況下,MySQL 還會自動重命名外鍵所需的索引。
drop_swap
禁用外鍵檢查(FOREIGH_KEY_CHECKS=0),先刪除原始表,然后將新表重命名到原來的位置。這與交換新舊表的方法不同,后者使用的是客戶端應用程序無法檢測到的原子 RENAME。
- 這種方式更快,但是有一些風險:在 drop 原表和 rename 臨時表之間的一段時間,DDL 的表不存在,查詢這個表的語句將會返回報錯。如果 rename 執行失敗,沒有修改成原表名稱,但是原表已經被永久刪除。
- 這種方式強制使用 --no-swap-tables 和 --no-drop-old-table。
none
這種方式和 drop_swap 類似,但是沒有 swap。任何引用原表的外鍵將會指向一個不存在的表,這樣會造成外鍵違規,在 show engine innodb status 中將會有類似下面的輸出:
Trying to add to index `idx_fk_staff_id` tuple:
DATA TUPLE: 2 fields;
0: len 1; hex 05; asc ;;
1: len 4; hex 80000001; asc ;;
But the parent table `sakila`.`staff_old`
or its .ibd file does not currently exist!
這是因為原始表(在本例中為 sakila.staff)被重命名為 sakila.staff_old,然后 drop 掉了。提供了這種處理外鍵約束的方法,以便數據庫管理員可以根據需要禁用該工具的內置功能。
--only-same-schema-fks
- 只在與原始表相同數據庫的表上檢查外鍵。這個選項是危險的,因為如果 fks 引用不同數據庫中的表,將不會被檢測到。
--null-to-not-null
- 允許將允許空值的列修改為不允許空值的列。包含空值的行將被轉換為定義的默認值。如果沒有給出明確的默認值,MySQL 會根據數據類型指定一個默認值,例如數字數據類型為 0,字符串數據類型為空
--[no]analyze-before-swap
- 默認值:yes
- 在與舊表 swap 之前,在新表上執行 ANALYZE TABLE。在 MySQL 5.6 及更高版本,innodb_stats_persistent 開啟的情況下,默認是 yes。
說明:innodb_stats_persistent 為 ON,表示統計信息會持久化存儲,OFF 表示統計信息只存儲在內存。
--[no]drop-new-table
- 默認值:yes
- 如果復制原始表失敗,則刪除新表。
- 指定 --no-drop-new-table 和 --no-swap-tables 將保留表的新修改副本,而不修改原始表,見 --new-table-name。
- --no-drop-new-table 不能和 --alter-foreign-keys-method drop_swap 同時使用。
--[no]drop-old-table
- 默認值:yes
- 重命名后刪除原始表。在原表被成功重命名以讓新表取而代之之后,如果沒有錯誤,該工具將在默認情況下刪除原表。如果有任何錯誤,該工具將保留原始表。如果指定了 --no-swap-tables,則不刪除舊表。
--[no]swap-tables
- 默認值:yes
- 交換原始表和修改后的新表。這一步通過使具有新模式的表取代原來的表,從而完成了在線模式更改過程。原始表變成舊表,工具會刪除它,除非禁用 --[no]drop-old-table。
使用 --no-swap-tables 會運行整個過程,它會創建新表,復制所有行但最后會刪除新表。它的目的是運行一個更現實的演練。
--[no]drop-triggers
- 默認值:yes
- 指定在舊表上刪除觸發器。--no-drop-old-table 強制 --no-drop-triggers。
--preserve-triggers
- 在指定時保留舊觸發器。在 MySQL 5.7.2 中,可以為一個給定的表定義具有相同觸發事件和動作時間的多個觸發器。這允許我們添加 pt-online-schema-change 所需的觸發器,即使表已經有了自己的觸發器。如果啟用了此選項,那么在開始從原始表復制行之前,pt-online-schema-change 將嘗試將所有現有觸發器復制到新表,以確保在修改表之后可以應用舊觸發器。
例如:
CREATE TABLE test.t1 (
id INT NOT NULL AUTO_INCREMENT,
f1 INT,
f2 VARCHAR(32),
PRIMARY KEY (id)
);CREATE TABLE test.log (
ts TIMESTAMP,
msg VARCHAR(255)
);CREATE TRIGGER test.after_update
AFTER
UPDATE ON test.t1
FOR EACH ROW
INSERT INTO test.log
VALUES (NOW(), CONCAT("updated row row with id ", OLD.id, " old f1:", OLD.f1, " new f1: ", NEW.f1 ));
- 對于這個表和觸發器組合,不可能使用 --preserve-triggers 和 --alter “DROP COLUMN f1”,因為觸發器引用被刪除的列,會導致觸發器失敗。
- 在測試觸發器將在新表上工作之后,觸發器將從新表中刪除,直到所有行都被復制,然后它們被重新應用。
- --preserve-triggers 不能與 --no-drop-triggers,--no-drop-old-table,--no-swap-tables 這些參數一起使用,因為 --preserve-triggers 意味著舊的觸發器應該被刪除并在新表中重新創建。由于不可能有多個具有相同名稱的觸發器,因此必須刪除舊的觸發器,以便能夠在新表中重新創建它們。
- 使用 --preserve-trigger 和 --no-swap-tables 將導致原始表的觸發器仍然被定義。如果同時設置了 --no-swap-tables 和 --no-drop-new-table,那么觸發器將保留在原始表上,并將復制到新表上(觸發器將具有隨機后綴,因為沒有唯一的觸發器名稱)。
--new-table-name
- type: string; default: %T_new
- 在交換表之前新建表名。將 %T 替換為原始表名。當使用默認值時,將在名稱前添加最多 10 個 '_'(下劃線),以查找唯一的表名稱。如果指定了表名,則不會將其作為前綴,因此該表必須不存在。
--force
- 在使用 --alter-foreign-keys-method = none 的情況下,這個選項會繞過確認。
--tries
- 類型:array
- 遇到錯誤時,嘗試的次數。下面是重試操作,以及它們的默認嘗試次數和嘗試之間的等待時間(以秒為單位)。

例子:
--tries create_triggers:5:0.5,drop_triggers:5:0.5
格式:
operation:tries:wait[,operation:tries:wait]
- 必須同時指定 3 個值:operation,tries,wait
注意:大多數操作只在 MySQL 5.5 和更新版本中受到 lock_wait_timeout(參見 --set-vars)的影響,因為元數據鎖。
對于創建和刪除觸發器,嘗試的次數應用于每個觸發器的 create trigger 和 drop trigger 語句。
對于復制行,嘗試的次數適用于每個塊,不是整個 table。
對于交換表,嘗試的次數通常只應用一次,因為通常只有一個 rename table 語句。
對于重新構建外鍵約束,每個語句都有相應的嘗試次數(用于重新構建約束的 alter 語句:--alter-foreign-keys-method;drop_swap 方法的其他語句)
下面這些錯誤出現時,將會重試,
Lock wait timeout (innodb_lock_wait_timeout and lock_wait_timeout)
Deadlock foundQuery is killed (KILL QUERY <thread_id>)
Connection is killed (KILL CONNECTION <thread_id>)
Lost connection to MySQL
錯誤和重新嘗試次數被記錄在 --statistics 中。
④ 負載相關
--critical-load
- 類型:Array;默認值:Threads_running=50
- 在復制每個 chunk 之后檢查 SHOW GLOBAL STATUS,如果負載太高則終止。該選項接受以逗號分隔的 MySQL 狀態變量和閾值列表。格式:variable=MAX_VALUE(或:MAX_VALUE)。如果沒有給出,該工具通過在啟動時檢查默認并將其加倍來確定閾值。
- 參見 --max-load 了解更多細節。不同的是,超過此選項指定的值時終止執行而不是暫停。使用該選項,可以作為一種安全檢查,以防當原始表上的觸發器給服務器增加過多負載導致停機。
--max-flow-ctl
- 類型:float
- 有點類似于 --max-lag,但是是針對 PXC 集群的。檢查用于流控制的集群平均暫停時間,如果超過選項中所示的百分比,則讓工具暫停。當檢測到任何流控制活動時,0 值將使工具暫停。默認是沒有流控制檢查。該選項可用于 PXC 版本 5.6 或更高版本。
--max-load
- type: Array; default: Threads_running=25
- 復制每個塊后,檢查 SHOW GLOBAL STATUS,如果任何狀態變量高于其閾值,則暫停執行。格式:variable=MAX 值 ( 或:MAX 值)。如果沒有指定,該工具通過檢查當前值并將其增加 20% 來確定一個閾值。
--sleep
- 類型:float,默認值:0
- 指定 copy 完每個 chunck 后,sleep 多久。當無法通過 --max-lag 和 --max-load 進行節流時,此選項非常有用。應該使用較小的,sub-second 值,例如 0.1,否則工具將會花費較長的時間來拷貝大表。
⑤ 配置類
--charset "string", -A
- 指定默認字符集,連接到 MySQL 后執行 set names character。
--default-engine
- 使用系統默認的存儲引擎創建新表。
- 默認情況下,創建的新表和原表 engine 相同。當指定該選項時,則去掉建表語句中的 engine 選項,使用系統默認的存儲引擎創建新表。
--defaults-file, -F
- 指定配置文件,需指定絕對路徑。
--data-dir
- 指定新表的數據文件所在目錄。僅可在 5.6 及以上版本使用。如果與 --remove-data-dir 同時使用,則忽略該參數。
--remove-data-dir
- 如果原始表是使用 DATA DIRECTORY 指定了數據文件目錄,刪除它并在 MySQL 默認數據目錄中創建新表,而不創建新的 isl 文件。
--set-vars
- 設置 MySQL 變量列表:variable=value,以逗號分隔。
- 默認情況下,該工具設置下面幾個默認變量:
wait_timeout=10000
innodb_lock_wait_timeout=1
lock_wait_timeout=60
--config
- 指定配置文件列表,用逗號分隔,如果指定這個選項,必須是命令行的第一個選項。
--pause-file "string"
- 當此參數指定的文件存在時,將暫停執行 DDL。比如,當 DDL 影響業務時,可創建指定的文件,暫停 DDL。
Sleeping 60 seconds because /tmp/a.txt exists
⑥ 復制 chunk 類
--chunk-size
- 指定每個復制塊的行數,默認值:1000。可指定單位:k,M,G。
- 默認復制塊的行為是:動態地調整塊大小,試圖使塊在 --chunk-time 秒內復制完成。當沒有顯式設置此選項時,將使用其默認值作為起點,之后將忽略此選項的值。當如果顯示指定該選項時,將禁用動態調整復制塊的行為。
--chunk-time
- 指定復制每個數據塊所需要的時間。類型:float;默認值:0.5。
- 使用該選項可動態調整塊大小,通過跟蹤復制率(每秒的行數),并在復制每個數據塊之后調整塊大小,以使復制下一個數據塊執行該選項指定的時間(以秒為單位)。
- 如果將此選項設置為零,則塊大小不會自動調整;因此復制每個數據塊時間將會變化,但復制塊大小不會變化。
--chunk-size-limit
- 復制塊的最大限制。類型:float;默認值:4.0。
- 當表沒有唯一索引時,塊大小可能不準確。此選項指定錯誤的最大可容忍限制。該工具使用 <EXPLAIN> 來估計塊中有多少行。如果估計值超過了期望的塊大小乘以限制,那么該工具將跳過該塊。
- 這個選項的最小值是 1,這意味著任何塊都不能大于 --chunk-size。可以通過指定值 0 來禁用過大塊檢查。
--chunk-index
- 指定對表進行分塊的索引(FORCE index),如果指定索引不存在,那么工具將使用默認的方式選擇索引。
--chunk-index-columns
- 指定只使用復合索引中最左邊的這么多列。這在 MySQL 查詢優化器中的一個 bug 導致它掃描大范圍的行,而不是使用索引精確地定位起始點和結束點的情況下非常有用。
⑦ slave 相關
--slave-user
- 類型:字符串
- 指定連接從庫的用戶。這個用戶可以有很少的權限,但是用戶一定要是存在的。
--slave-password
- 類型:字符串
- 指定連接到從庫的密碼,可以和 --slave-user 一塊使用,指定的用戶和密碼在所有從庫上必須是一樣的。
--channel
- 指定使用復制通道連接到服務器時使用的通道名稱。
- 適用場景:多源復制情況下,show slave status 會返回兩行,使用此選項指定復制通道。
--max-lag
- type: time; default: 1s
- 指定當從庫復制延遲大于該值時,暫停 data copy,直到所有復制的延遲都小于這個值。
- 在復制完每個塊后,該工具會連接到所有從庫,查看其復制延遲(Seconds_Behind_Master)。如果任何從庫的延遲時間超過此選項的值,則工具將休眠 --check-interval 指定的時間,然后再次檢查所有從庫。如果指定 --check-slave-lag,那么該工具只檢查該服務器的延遲,而不是所有服務器。如果希望準確地控制該工具檢測哪些服務器,可以使用 --recursion-method 指定 DSN 值。
- 該工具永遠等待從實例停止延遲。如果任何從實例停止,該工具將永遠等待,直到從實例啟動。
--recurse
- type: int
- 發現從實例時在層次結構中要遞歸的級別數。默認是無限的。
--recursion-method
- type:array; 默認值:processlist,host
- 用于判斷是否存在從庫的方式,可以的方式有:processlist:show processlist;hosts:show slave hostsdsn=DSN:DSNs from a tablenone:不查找從庫
⑧ check 類
--check-interval
- 指定檢查 --max-lag 的時間間隔,默認值 1。如果任何從庫的延遲時間超過 --max-lag 的值,將休眠 --check-interval 指定的時間,然后再次檢查。
--check-slave-lag
- 指定檢查延遲的從庫,以DSN的方式指定。當延遲超過 --max-lag 時,將暫停 data copy。
--skip-check-slave-lag
- 指定 DSN,跳過檢查指定從庫延遲,可以指定多個, 例如:
–skip-check-slave-lag h=127.0.0.1,P=12345 –skip-check-slave-lag h=127.0.0.1,P=12346
--[no]check-replication-filters
- 檢查從庫是否設置 replication filter,如 binlog_ignore_db 和 replicate_do_db,默認值為 yes。如果設置了,則中止執行。因為如果更新的表 Master 上存在,而 Slave 上不存在,會導致復制失敗。使用 --no-check-replication-filters 選項來禁用該檢查。
--[no]check-alter
- 解析 --alter 指定的值,并警告可能的意外行為,默認值:yes。目前,它檢查的有:列名:該工具的早期版本中,用 CHANGE COLUMN name new_name 重命名列會導致該列的數據丟失。現在會嘗試解析 alter 語句并捕捉這些情況,因此重命名的列應該具有與原始列相同的數據。但是,執行此操作的代碼并不是一個成熟的 SQL 解析器,因此應該首先使用 --dry-run 和 --print 運行該工具,并驗證它是否正確地檢測到重命名的列。drop primary key:如果 --alter 包含 DROP PRIMARY KEY(大小寫和空格不敏感),則會打印警告并退出,除非指定 --dry-run。更改主鍵可能是危險的,但是工具可以處理它。工具觸發器,特別是 DELETE 觸發器,最容易受到主鍵更改的影響。因此應該首先使用 --dry—run 和 -- print 運行該工具,并驗證觸發器是否正確。
--[no]check-plan
- 檢查 SQL 執行計劃。默認值 yes,則在執行 SQL 前執行 EXPLAIN,如果 MySQL 選擇了一個糟糕的執行計劃,會導致訪問很多行,該工具將跳過表的 chunk。
- 該工具使用很多個方式來決定執行計劃是否糟糕。
--[no]check-unique-key-change
- 默認值為 yes,如果 --alter 的指定語句試圖添加惟一索引,將不會執行,并打印一個 select 語句用于檢查列上是否有重復記錄。
- 因為 pt-online-schema-change 使用 INSERT IGNORE 將行復制到新表,所以如果正在寫入的行主鍵沖突,不會報錯,數據將丟失。
--[no]version-check
- 默認值:yes
- 檢查 Percona Toolkit、MySQL 和其他程序的最新版本。