1. 程式人生 > 其它 >MySQL——pt-online-schema-change工具的使用

MySQL——pt-online-schema-change工具的使用

作用:功能為在 alter 操作更改表結構的時候不用鎖定表,也就是說執行 alter 的時候不會阻塞寫和讀取操作 常見引數: --alter指定ALTER 語句,正常的ALTER TABLE TBNAME [ ADD | MODIFY | DROP | ALTER ] COLUMN COLUMN_NAME ...,去除前面的ALTER TABLE TABLE那麼,直接指定後部分的內容 注意:rename 不支援,請直接使用renametabletable_nameto table_name_new; 如果表有資料,建立非空無預設值的列,會失敗;如果非空,需要指定預設值, 如果直接使用addcolumn num int, num這個列的預設值為NULL; 如果表有資料,為一個列新增預設值時,舊資料為NULL的是不會被修改,依舊為NULL,以後新加入的資料則會預設設定為預設值 對於外來鍵的刪除情況,由於執行是在新表上執行DDL,所以其外來鍵值的命名跟原表的命名不一樣,假設刪除原表的外來鍵名是 fk_foo,那麼新表的外來鍵名就為 _fk_foo,所以刪除的ALTER語句是: drop foreign key _fk_foo --alter-foreign-keys-method 如果修改的表,是其他表外來鍵reference的表,那麼,最後rename的過程,需要確保一定成功,要不然這些子表就沒能成功reference到其指定的表名,對子表的操作將會報錯。比如 tba有一個外來鍵 fk_tba引用表格 tbb,這個時候tbb需要做DDL操作,根據pt工具的原理得知,最後會有一個rename環節,這個環節可能會導致約束失效或者執行堵塞等問題 針對最後的rename這個環節,該工具提供了4種處理方法: auto 自動選擇rebuild_constraints 或者 drop_swap,優先選擇rebuild_constraints rebuild_constraints:指在rename table前,先刪除子表的外來鍵約束,然後重建外來鍵約束指向到新表(ALTER TABLE語句新增),最後執行rename操作 注意:rename操作即使不成功,它也rename到新表,不會出現reference的表不存在情況,其弊端如果子表過大,新增外檢約束的過程中,可能會對子表造成阻塞 drop_swap:執行rename之前禁用外來鍵檢查,然後刪除原表,rename新表為原表名,這個執行過程非常快並且沒有阻塞 注意:需要強制指定 --no-swap-tables 跟 --no-drop-old-table,其弊端當把原表刪除而新表還沒rename為原表的名字時,這段時間實際非常短,但是這段時 間內,等於原表名的表是不存在的,子表做一些DML的時候,可能會出現錯誤。rename期間,如果新表rename原表失敗,但是已經刪除原表,那麼這段期間,其子表 的操作將會出現大面積問題,直到人工修復 none:類似於drop_swap,不同在於對原表的處理 按正常的pt工具流程,禁用外來鍵約束,rename原表為臨時表,rename新表為原表名,刪除臨時表 弊端: 當把原表rename為臨時表,而新表還沒rename為原表的名字時,這段時間實際非常短,但是這段時間內,等於原表名的表格時不存在的,子表做一些DML的時 候,可能會出現錯誤 --drop-old-tables 操作成功後,原表是否保留,預設是刪除;default:yes 可選:--no-drop-old-table --dry-run 僅建立新表,但是不執行觸發器、拷貝資料和替換原表 --execute 確認執行alter操作,注意,這個操作如果不指定,則僅做安全檢查然後退出 --host 連線主機名 --max-lag 預設1秒 檢查從庫延遲的時間,如果超過,則停止copy data,休息--check-interval秒後,再重新開始copy資料; 檢視通過延遲時間,是通過從庫show slave status,檢視Seconds_Behind_Master; 如果指定--check-slave-lag,該工具只檢查該伺服器的延遲,而不是所有伺服器; --check-interval 預設1秒 從庫延遲超過指定的--max-lag,中斷copy data休息的時間 --max-load 預設Threads_runing=25, --max-load=Threads_running=15 copy data的過程,監控資料庫當前正在執行的thread,如果超過指定的Threads_running值,則停止拷貝資料,會在輸出的內容中答應 Pausing because Threads_runing=15,直到執行的執行緒數小於給定的值,恢復copy data,如此迴圈,知道拷貝資料結束. --password 資料庫使用者名稱密碼 --port 資料庫埠號 --socket 資料庫socket檔案 --user 資料庫使用者名稱 --recursion-method master尋找slave的方式,有4中方式 processlist show processlist hosts show slave hosts dsn=DNS DSNs from a table none do not find slaves 不查詢從庫 注意:dsn,使用表格 tdsn儲存從庫資訊(DSN的具體引數選項可以詳細檢視 3.3 DSN選線) 需要手動在需要DDL的資料庫內,建立 dsns 表格 CREATE TABLE `dsns` (`id` int(11) NOT NULL AUTO_INCREMENT,`parent_id` int(11) DEFAULT NULL,`dsn` varchar(255) NOT NULL, PRIMARY KEY (`id`)); 儲存從庫資訊 insert into dsns(dsn) values(h=slave_host,u=repl_user,p=repl_password,P=port ); 該引數使用的時候,按以下格式(假設 dsns表格建立在資料庫 dbosc) --recursion-method dsn=D=dbosc,t=dsns --statistics 增加影響行數列印,可以檢視copy進度 --print 詳細列印alter過程,不指定的時候,簡略列印 主庫準備資料: create table t1 (id int, name varchar(10)); delimiter ;; create procedure idata() begin declare i int; set i=1; while(i<=100000)do insert into t1 values(i,i); set i=i+1; end while; end;; delimiter ; call idata(); 安裝依賴: yum -y install perl-DBI yum -y install perl-DBD-MySQL yum -y install perl-Time-HiRes yum -y install perl-IO-Socket-SSL 檢視幫助文件 [root@db201 percona-toolkit-3.3.1]# ./bin/pt-online-schema-change --help Can't locate Digest/MD5.pm in @INC (@INC contains: /usr/local/lib64/perl5 /usr/local/share/perl5 /usr/lib64/perl5/vendor_perl /usr/share/perl5/vendor_perl /usr/lib64/perl5 /usr/share/perl5 .) at ./bin/pt-online-schema-change line 6340. BEGIN failed--compilation aborted at ./bin/pt-online-schema-change line 6340. 原因是缺少一個perl-Digest-MD5的包,只需要安裝即可: yum -y install perl-Digest-MD5 [root@db201 ~]# /root/percona-toolkit-3.3.1/bin/pt-online-schema-change --socket=/root/data3307/my3307.sock --user=root --password=rootD=db1,t=t1 --alter "add column num int "--recursion-method=none --no-check-replication-filters --alter-foreign-keys-method auto --print --execute Cannot connect to MySQL: DBI connect('db1;mysql_socket=/root/data3307/my3307.sock;mysql_read_default_group=client','root',...) failed: Authentication plugin 'caching_sha2_password' cannot be loaded: /usr/lib64/mysql/plugin/caching_sha2_password.so: 無法開啟共享物件檔案: 沒有那個檔案或目錄 at /root/percona-toolkit-3.3.1/bin/pt-online-schema-change line 2345. 原因:mysql8.0.24的版本的加密插接default_authentication_plugin為caching_sha2_password;需要將加密外掛改為mysql_native_password [root@db201 ~]# /root/percona-toolkit-3.3.1/bin/pt-online-schema-change --host=192.168.221.201 --user=root --password=rootP=3307,D=db1,t=t1 --alter "add column num int "--recursion-method=none --no-check-replication-filters --alter-foreign-keys-method auto --print --execute No slaves found.See --recursion-method if host db201 has slaves. Not checking slave lag because no slaves were found and --check-slave-lag was not specified. ******************************************************************* Using the default of SSL_verify_mode of SSL_VERIFY_NONE for client is deprecated! Please set SSL_verify_mode to SSL_VERIFY_PEER possibly with SSL_ca_file|SSL_ca_path for verification. If you really don't want to verify the certificate and keep the connection open to Man-In-The-Middle attacks please set SSL_verify_mode explicitly to SSL_VERIFY_NONE in your application. ******************************************************************* at /root/percona-toolkit-3.3.1/bin/pt-online-schema-change line 7119. ******************************************************************* Using the default of SSL_verify_mode of SSL_VERIFY_NONE for client is deprecated! Please set SSL_verify_mode to SSL_VERIFY_PEER possibly with SSL_ca_file|SSL_ca_path for verification. If you really don't want to verify the certificate and keep the connection open to Man-In-The-Middle attacks please set SSL_verify_mode explicitly to SSL_VERIFY_NONE in your application. ******************************************************************* at /root/percona-toolkit-3.3.1/bin/pt-online-schema-change line 7119. # A software update is available: Cannot chunk the original table `db1`.`t1`: There is no good index and the table is oversized. at /root/percona-toolkit-3.3.1/bin/pt-online-schema-change line 6012. 原因是:t1表沒有主鍵或者唯一索引
——t1表新增主鍵 alter table t1 add primary key (id);後測試 ——新增一列 [root@db201 ~]# /root/percona-toolkit-3.3.1/bin/pt-online-schema-change --host=192.168.221.201 --user=root --password=rootP=3307,D=db1,t=t1 --alter "add column num int"--recursion-method=none --no-check-replication-filters --alter-foreign-keys-method auto --print --execute No slaves found.See --recursion-method if host db201 has slaves. Not checking slave lag because no slaves were found and --check-slave-lag was not specified. 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 No foreign keys reference `db1`.`t1`; ignoring --alter-foreign-keys-method. Altering `db1`.`t1`... Creating new table... CREATE TABLE `db1`.`_t1_new` ( `id` int NOT NULL, `name` varchar(10) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 Created new table db1._t1_new OK. Altering new table... ALTER TABLE `db1`.`_t1_new` add column num int Altered `db1`.`_t1_new` OK. 2021-11-23T02:02:17 Creating triggers... ----------------------------------------------------------- Event : DELETE Name: pt_osc_db1_t1_del SQL: CREATE TRIGGER `pt_osc_db1_t1_del` AFTER DELETE ON `db1`.`t1` FOR EACH ROW BEGIN DECLARE CONTINUE HANDLER FOR 1146 begin end; DELETE IGNORE FROM `db1`.`_t1_new` WHERE `db1`.`_t1_new`.`id` <=> OLD.`id`; END Suffix: del Time: AFTER ----------------------------------------------------------- ----------------------------------------------------------- Event : UPDATE Name: pt_osc_db1_t1_upd SQL: CREATE TRIGGER `pt_osc_db1_t1_upd` AFTER UPDATE ON `db1`.`t1` FOR EACH ROW BEGIN DECLARE CONTINUE HANDLER FOR 1146 begin end; DELETE IGNORE FROM `db1`.`_t1_new` WHERE !(OLD.`id` <=> NEW.`id`) AND `db1`.`_t1_new`.`id` <=> OLD.`id`; REPLACE INTO `db1`.`_t1_new` (`id`, `name`) VALUES (NEW.`id`, NEW.`name`); END Suffix: upd Time: AFTER ----------------------------------------------------------- ----------------------------------------------------------- Event : INSERT Name: pt_osc_db1_t1_ins SQL: CREATE TRIGGER `pt_osc_db1_t1_ins` AFTER INSERT ON `db1`.`t1` FOR EACH ROW BEGIN DECLARE CONTINUE HANDLER FOR 1146 begin end; REPLACE INTO `db1`.`_t1_new` (`id`, `name`) VALUES (NEW.`id`, NEW.`name`);END Suffix: ins Time: AFTER ----------------------------------------------------------- 2021-11-23T02:02:17 Created triggers OK. 2021-11-23T02:02:17 Copying approximately 100259 rows... INSERT LOW_PRIORITY IGNORE INTO `db1`.`_t1_new` (`id`, `name`) SELECT `id`, `name` FROM `db1`.`t1` FORCE INDEX(`PRIMARY`) WHERE ((`id` >= ?)) AND ((`id` <= ?)) LOCK IN SHARE MODE /*pt-online-schema-change 40669 copy nibble*/ SELECT /*!40001 SQL_NO_CACHE */ `id` FROM `db1`.`t1` FORCE INDEX(`PRIMARY`) WHERE ((`id` >= ?)) ORDER BY `id` LIMIT ?, 2 /*next chunk boundary*/ 2021-11-23T02:02:18 Copied rows OK. 2021-11-23T02:02:18 Analyzing new table... 2021-11-23T02:02:18 Swapping tables... RENAME TABLE `db1`.`t1` TO `db1`.`_t1_old`, `db1`.`_t1_new` TO `db1`.`t1` 2021-11-23T02:02:18 Swapped original and new tables OK. 2021-11-23T02:02:18 Dropping old table... DROP TABLE IF EXISTS `db1`.`_t1_old` 2021-11-23T02:02:18 Dropped old table `db1`.`_t1_old` OK. 2021-11-23T02:02:18 Dropping triggers... DROP TRIGGER IF EXISTS `db1`.`pt_osc_db1_t1_del` DROP TRIGGER IF EXISTS `db1`.`pt_osc_db1_t1_upd` DROP TRIGGER IF EXISTS `db1`.`pt_osc_db1_t1_ins` 2021-11-23T02:02:18 Dropped triggers OK. Successfully altered `db1`.`t1`. 注意:新新增的列,預設值為NULL
利用pt-online-schema-change 新增索引,檢視pt-online-schema-change 具體的執行流程 [root@db201 ~]# /root/percona-toolkit-3.3.1/bin/pt-online-schema-change --host=192.168.221.201 --user=root --password=rootP=3307,D=db1,t=t1 --alter "ADD INDEX name(name) "--recursion-method=none --no-check-replication-filters --alter-foreign-keys-method auto --print --execute 在執行之前,開啟set globalgeneral_log=1;檢視統一日誌,如下: [root@db201 data]# cat db201.log 省略 由以上的日誌,可以看出具體的執行流程如下:
  • 相關環境引數檢查;
  • 檢查該表是否存在;
  • show create table t1;
  • CREATE TABLE `db1`.`_t1_new`
  • ALTER TABLE `db1`.`_t1_new` ADD INDEX name(name)
  • 建立刪除觸發器CREATE TRIGGER `pt_osc_db1_t1_del`
  • 建立更新觸發器CREATE TRIGGER `pt_osc_db1_t1_upd`
  • 建立插入觸發器CREATE TRIGGER `pt_osc_db1_t1_ins`
  • 按塊拷貝資料到新表,拷貝過程中對資料行持有S鎖
  • analyze新表ANALYZE TABLE `db1`.`_t1_new` /* pt-online-schema-change */
  • rename表名RENAME TABLE `db1`.`t1` TO `db1`.`_t1_old`, `db1`.`_t1_new` TO `db1`.`t1`
  • 刪除舊錶DROP TABLE IF EXISTS `db1`.`_t1_old`
  • 刪除新表上的刪除,更新,插入觸發器 DROP TRIGGER IF EXISTS `db1`.`pt_osc_db1_t1_del`
Q1、alter操作期間,t1表是否支援DML操作?? ALTER過程採用Copy Table To New Table的方式,新建一個表t1,然後在原表上建立3個觸發器:DELETE\UPDATE\INSERT觸發器,拷貝資料到新表的過程中,如果原表資料發生變化,則會通過觸發器更新到新表上 INSERT原表的時候,觸發器根據其主鍵ID把新紀錄INSERT到新表上; UPDATE原表的時候,觸發器根據其主鍵ID判斷新舊ID是否一致,如果一致則刪除,然後在REPLACE INTO新紀錄到新表 DELETE原表的時候,觸發器根據其主鍵ID直接刪除行記錄 如果資料修改的時候,還沒有拷貝到新表,修改後再拷貝,雖然重複覆蓋,但是資料也沒有出錯;如果是資料已經拷貝,原表發生修改,這時觸發器同步修改資料,兩種情況下都保證了資料的一致性; Q2、整個操作流程鎖情況是什麼樣的?? 建立新表後,按照每一個chunk的大小拷貝資料到新表,每次SELECT都是share mode,帶S鎖,但是每個chunk都比較小,所以鎖時間不大; 最後資料拷貝結束,會有一個rename操作,這個操作過程中,是不支援DML操作的,但其速度很快,不會造成長時間鎖表情況; 該工具會設定該DDL操作的鎖等待超時為1s,當出現異常的時候,會是ALTER操作異常,而不是其他業務操作異常,這樣可以最大程度的不影響其他事務的進行; Q3、執行期間有什麼效能影響?? 總體而言,對資料庫的鎖影響降低到了最小,執行期間允許DML操作; 但是注意,任何DDL SQL在這裡,都是轉換成copy table to new table的形式,這個過程中,會極大佔用磁碟的IO跟CPU資源,同時給主從複製帶來一定的影響; copy data過程中,如果主從延遲異常超過 max-lag則停止copy data,等待主從延遲恢復,預設為1min,可以通過--max-lag設定; 檢測到伺服器負載異常,也會停止操作,可以通過 --max-load,--critical-load設定 Q4、該工具有什麼限制情況?? 1、表格必須帶有主鍵或者唯一索引 2、存在複製過濾掉表格,ALTER操作 3、copy data過程中,如果主從延遲異常超過 max-lag則停止copy data,等待主從延遲恢復,預設為1s,可以通過--max-lag設定 4、檢測到伺服器負載異常,也會停止操作,可以通過 --max-load,--critical-load設定 5、設定操作的鎖等待超時為1s,當出現異常的時候,ALTER操作異常,而不是其他業務操作異常,這樣可以最大程度的不影響其他事務的進行 6、預設情況下,存在 被外來鍵引用的表格是不支援ALTER操作的,除非手動指定引數--alter-foreign-keys-method 7、不支援修改 Percona XtraDB Cluster (PXC)上節點的 myisam表格 Q5、在binlog日誌中是怎麼記錄這個操作的?? 會將整個alter的流程全部記錄下來 [root@db201 binlog]# ~/mysql8024/bin/mysqlbinlog--no-defaults --base64-output=decode-rows -v mysql_bin.000002 >2.sql 省略。。。。。。和general_log日誌記錄的詳細步驟一致 修改某一列的預設值: mysql> show create table t1\G; *************************** 1. row *************************** Table: t1 Create Table: CREATE TABLE `t1` ( `id` int NOT NULL, `name` varchar(10) DEFAULT NULL, `num` int DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 /root/percona-toolkit-3.3.1/bin/pt-online-schema-change --host=192.168.221.201 --user=root --password=rootP=3307,D=db1,t=t1 --alter "ALTER column num SET DEFAULT 100 "--recursion-method=none --no-check-replication-filters --alter-foreign-keys-method auto --print --execute mysql> show create table t1\G; *************************** 1. row *************************** Table: t1 Create Table: CREATE TABLE `t1` ( `id` int NOT NULL, `name` varchar(10) DEFAULT NULL, `num` int DEFAULT '100', PRIMARY KEY (`id`), KEY `name` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 mysql> insert into t1(id,name) values(200000,'200000'); Query OK, 1 row affected (0.01 sec) mysql> select * from t1 where id=200000; +--------+--------+------+ | id| name| num| +--------+--------+------+ | 200000 | 200000 |100 | +--------+--------+------+ mysql> select count(*) from t1 where num=100; +----------+ | count(*) | +----------+ |1 | 驗證了:如果表有資料,為一個列新增預設值時,舊資料為NULL的是不會被修改,依舊為NULL,以後新加入的資料則會預設設定為預設值 考慮從庫延遲的情況: 考慮從庫延遲情況 ,意味這要注意這幾個選項的設定 --max-lag --check-interval --recursion-method --check-slave-lag 從庫延遲超過max-lag則停止copy data,等待 check-interval 秒後再開始copy data。check-slave-lag指定slave的機器,只會對比這臺slave的延遲情況。recursion-method是主庫尋找從庫的方法,有四個方法:processlist,hosts,dsn,none,具體檢視上部分選項詳細說明 假如還需要給表t1新增一列:hobby varchar(100) ,需要考慮從庫的延遲情況 建立表格dsns,記錄從庫資訊 CREATE TABLE `dsns` ( `id` int(11) NOT NULL AUTO_INCREMENT, `parent_id` int(11) DEFAULT NULL, `dsn` varchar(255) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 #insert從庫資訊,有2個從庫,分別為202伺服器上的 3310跟3320 insert into dsns(dsn) select "h=192.168.221.202,u=repl,p=****,P=3310"; insert into dsns(dsn) select "h=192.168.221.202,u=repl,p=****,P=3320"; 如果需要考慮多個從庫的延遲情況,則可以考慮使用 dsns表格來記錄從庫資訊,如果只需要考慮某一臺從庫的延遲情況,則既可以使用dsns表格也可以使用引數--check-slave-lag指定從庫。 不考慮外來鍵關係,考慮從庫影響程度,檢查到從庫延遲超過1s,則休息5s,具體指令如下 pt-online-schema-change -P3307 --user=root --password=root D=db1,t=t1 --max-lag=1s --check-interval=10s --alter "ADD hobby varchar(100) NOT NULL DEFAULT ‘sleep‘ "--recursion-method dsn=D=db1,t=dsns--alter-foreign-keys-method auto --print --execute