17. pt-online-schema-change
在平時MySQL的運維過程中,經常會遇到表結構的變更。在表比較小的時候,直接進行變更,時間較短,但是當表非常大的時候,這麼做會導致應用卡死,服務不可用。
目前InnoDB引擎是通過以下步驟來進行DDL的:
1 利用DDL之後的語句建立一張臨時表
2 在原表上加write lock,阻塞所有DML操作
3 將原表資料複製到臨時表
4 將臨時表和原表重新命名,然後drop原始表
5 釋放 write lock。
在這個DDL過程中,針對大表進行的write lock將持續非常長的時間,我們可以用為此 perconal 推出一個工具 pt-online-schema-change,在進行DDL的時候不堵塞原表的讀寫。
工作原理:
如果表有外來鍵,除非使用 --alter-foreign-keys-method 指定特定的值,否則工具不予執行。
1 建立一張和原表一樣的空表結構。
2 執行空表的DDL
3 在原表上建立觸發器,將對原表的修改操作記錄下來。
4 複製資料到新的空表中,複製完成後,應用修改記錄。
注意:如果表中已經定義了觸發器這個工具就不能工作了。
5 複製完成後在重新命名原表和新的表
============================================
create table t02(id int);
pt-online-schema-change --alter="add name varchar(50) not null default ''" \
h=192.168.100.101,P=3306,u=admin,p=admin,D=db01,t=t02 \
--dry-run --print \
--execute
pt-online-schema-change --alter="modify id int unsigned not null auto_increment primary key" \
h=192.168.100.101,P=3306,u=admin,p=admin,D=db01,t=t02 \
--dry-run --print \
--execute
pt-online-schema-change --alter="add index idx_name_score(name,score)" \
h=192.168.100.101,P=3306,u=admin,p=admin,D=db01,t=t02 \
--dry-run --print \
--execute
pt-online-schema-change --alter="drop index idx_finger" \
h=192.168.100.101,P=3306,u=admin,p=admin,D=db01,t=t02 \
--dry-run --print \
--execute
pt-online-schema-change --alter="engine=innodb" \
h=192.168.100.101,P=3306,u=admin,p=admin,D=db01,t=t02 \
--dry-run --print \
--execute
================================================
【DSN】
指定時注意大小寫敏感,“=”左右不能有空格,多個值之間用逗號分隔
1. A charset
2. D database
3. F mysql_read_default_file
4. h host
5. p password
6. P port
7. S mysql_socket
8. t table
9. u user
【具體執行程序解析】
現在執行一個改表語句並開啟general log 觀察一下
1
pt-online-schema-change --alter 'add column c1 int' u=username,S=/data/mysql.sock,D=test,t=a --execute
1. 首先就是各種show,各種set,有興趣自己去看看,主要就是對許可權的檢查,超時時間的設定,當前系統的繁忙程度;
然後就是對錶的檢查,如是否有觸發器的存在,以及如下查詢:
explain SELECT * FROM `test`.`a` WHERE 1=1;
SELECT table_schema, table_name FROM information_schema.key_column_usage
WHERE referenced_table_schema='test' AND referenced_table_name='a';
SHOW CREATE TABLE `test`.`a`;
到這裡表的情況檢查完畢
2. 現在就開始建新表,注意名字的改變,a變成了_a_new
並在這個空表上直接alter
ALTER TABLE `test`.`_a_new` add column c1 int
然後做一下檢檢視alter是否成功
SHOW CREATE TABLE `test`.`_a_new`
3. 建立觸發器
CREATE TRIGGER `pt_osc_test_a_del` AFTER DELETE ON `test`.`a` FOR EACH ROW DELETE IGNORE
FROM `test`.`_a_new` WHERE `test`.`_a_new`.`id` <=> OLD.`id`
CREATE TRIGGER `pt_osc_test_a_upd` AFTER UPDATE ON `test`.`a` FOR EACH ROW
REPLACE INTO `test`.`_a_new` (`id`, `name`, `type`, `b`) VALUES (NEW.`id`, <br>NEW.`name`, NEW.`type`, NEW.`b`)
CREATE TRIGGER `pt_osc_test_a_ins` AFTER INSERT ON `test`.`a` FOR EACH ROW
REPLACE INTO `test`.`_a_new` (`id`, `name`, `type`, `b`) VALUES (NEW.`id`, <br>NEW.`name`, NEW.`type`, NEW.`b`)
4. 通過explain來判斷執行chunk拷貝的成本,第一個chunk的大小固定為1000行,後面的chunk根據自己的指定,如chunk-time來確定大小
EXPLAIN SELECT `id`, `name`, `type`, `b` FROM `test`.`a` FORCE INDEX(`PRIMARY`)
WHERE ((`id` >= '1')) AND ((`id` <= '1000')) LOCK IN SHARE MODE
確定不會影響系統正常執行後,執行insert操作,將原始表中的資料按照當前chunk大小拷貝到新表中
INSERT LOW_PRIORITY IGNORE INTO `test`.`_a_new` (`id`, `name`, `type`, `b`)
SELECT `id`, `name`, `type`, `b` FROM `test`.`a` FORCE INDEX(`PRIMARY`)
WHERE ((`id` >= '1')) AND ((`id` <= '1000')) LOCK IN SHARE MODE
5. 一個chunk拷貝結束後立即對系統負載進行檢查
SHOW GLOBAL STATUS LIKE 'Threads_running'
沒問題的話就繼續explain,insert,負載太高的話就暫停拷貝等待負載降低,以此類推,直到所有拷貝結束
6. 拷貝結束後,對新表狀態是否進行檢查
ANALYZE TABLE `test`.`_a_new`
如果正常OK就往下走,如果不OK就刪掉新表或不刪報錯退出(根據引數指定),預設是刪掉
7. 確定新表沒有問題後就用新表來代替舊錶,注意舊錶的名字在這個時候也會改一下
RENAME TABLE `test`.`a` TO `test`.`_a_old`, `test`.`_a_new` TO `test`.`a`
8. 替換成功後預設是將舊錶刪掉
DROP TABLE IF EXISTS `test`.`_a_old`
9. 將之前建的觸發器刪掉
DROP TRIGGER IF EXISTS `test`.`pt_osc_test_a_del`
DROP TRIGGER IF EXISTS `test`.`pt_osc_test_a_upd`
DROP TRIGGER IF EXISTS `test`.`pt_osc_test_a_ins`
10. 最後再確定一下新表是否改名成功
SHOW TABLES FROM `test` LIKE '\_a\_new'
SHOW TABLES FROM `test` LIKE 'a'
改表完成!!!