1 OSC介紹
在我們的數據庫操作中,更改表結構是一個常見的操作,而當我們的表數據量非常大時,我們更改表結構的時間是非
常的長,並且在跟改期間,會生成一個互斥鎖,阻塞對整個表的所有操作,這樣,對於我們線上數據來說是無法容忍
的,以往的做法中,為了不影響線上業務,我們一般采用:先在線下從庫更改表結構,然后替換線上從庫,這樣一台
台的修改,最后做一下主庫切換,這個過程會耗費很長時間,並且在做主庫切換時,風險也非常的大,OSC(Online
Schema Change)大多都是利用了觸發器的原理,實現了在線更改表結構的同時,避免了鎖表,同時還允許其他的dml操
作,目前已經有多種工具實現了 OSC 下面就幾種常見的的工具。
2 MySQL5.6 OnlineDDL
MySQL5.6 Online DDL可以做到DDL\DML\SELECT同時進行
示例
alter table test add name varchar(10),ALGORITHM=INPLACE ,LOCK=NONE;
#Locking Options for Online DDL
LOCK=DEFAULT
LOCK=NONE
LOCK=SHARED
LOCK=EXCLUSIVE
#Performance of In-Place versus Table-Copying DDL Operations
ALGORITHM=DEFAULT
ALGORITHM=INPLACE
ALGORITHM=COPY
實現細節
#Prepare階段
1.創建臨時frm文件
2.持有EXCLUSIVE-MDL鎖,禁止讀寫
3.根據ALTER類型,確定執行方式(copy,online-rebuild,online-norebuild)
4.更新數據字典的內存對象
5.分配row_log對象記錄增量
6.生成臨時ibd文件
#ddl執行階段
1.降級EXCLUSIVE-MDL鎖,允許讀寫
2.掃描原表的聚簇索引每條記錄
3.遍歷新表的聚簇索引和二級索引,逐一處理
4.根據記錄構造對應的索引項
5.將構造索引項插入sort_buffer塊
6.將sort_buffer塊插入新的索引
7.處理ddl執行過程中產生的增量(僅rebuild類型需要)
#commit階段
1.升級到EXCLUSIVE-MDL鎖,禁止讀寫
2.應用最后row_log中產的日志
3.更新innodb的數據字典表
4.提交事務(刷事務的redo日志)
5.修改統計信息
6.rename臨時idb文件,frm文件
7.變更完成
從上面可以看到 在開始 和 結束階段 還是鎖表了 只是縮短了鎖表的時間
以加索引為例,介紹 copy方式跟inplace方式的實現流程
#copy方式
1.新建帶索引(主鍵索引)的臨時表
2.鎖原表,禁止DML,允許查詢
3.將原表數據拷貝到臨時表
4.禁止讀寫,進行rename,升級字典鎖
5.完成創建索引操作
#inplace方式
1.創建索引(二級索引)數據字典
2.加共享表鎖,禁止DML,允許查詢
3.讀取聚簇索引,構造新的索引項,排序並插入新索引
4.等待打開當前表的所有只讀事務提交
5.創建索引結束
3 Percona公司的pt-online-schema-change
示例
pt-online-schema-change h=*,u=* p=**,P=* ,D=enk,t=my1 --alter "add is_sign_1 int(11) unsigned NOT NULL DEFAULT '0'" --drop-old-table [--sleep 10] --print --executeD=lots,t=t_o_tr
實現細節
1. 新建tmp_table,表結構同原表
CREATE TABLE `$db`.`$tmp_tbl` LIKE `$db`.`$tbl`"
2. 在tmp_table上更改表結構為需要的表結構
3. 在原表上建立三個觸發器,如下:
#delete 觸發器
CREATE TRIGGER mk_osc_del AFTER DELETE ON $table " "FOR EACH ROW "
"DELETE IGNORE FROM $new_table ""WHERE $new_table.$chunk_column = OLD.$chunk_column";
#insert 觸發器
CREATE TRIGGER mk_osc_ins AFTER INSERT ON $table " "FOR EACH ROW "
"REPLACE INTO $new_table ($columns) " "VALUES($new_values)";
#update 觸發器
CREATE TRIGGER mk_osc_upd AFTER UPDATE ON $table " "FOR EACH ROW "
"REPLACE INTO $new_table ($columns) " "VALUES ($new_values)";
#我們可以看到這三個觸發器分別對應於INSERT、UPDATE、DELETE三種操作,
mk_osc_del,DELETE操作,我們注意到DELETE IGNORE,當新有數據時,我們才進行操作,也就是說,當在后續導入過程中,如果刪除
的這個數據還未導入到新表,那么我們可以不在新表執行操作,因為在以后的導入過程中,原表中改行數據已經被刪除,已經沒有數據,那
么他也就不會導入到新表中;
mk_osc_ins,INSERT操作,所有的INSERT INTO全部轉換為REPLACE INTO,為了確保數據的一致性,當有新數據插入到原表時,如果
觸發器還未把原表數據未同步到新表,這條數據已經被導入到新表了,那么我們就可以利用replace into進行覆蓋,這樣數據也是一致的。
mk_osc_upd UPDATE操作,所有的UPDATE也轉換為REPLACE INTO,因為當跟新的數據的行還未同步到新表時,新表是不存在這條記錄
的,那么我們就只能插入該條數據,如果已經同步到新表了,那么也可以進行覆蓋插入,所有數據與原表也是一致的;
#我們也能看出上述的精髓也就這這幾條replace into操作,正是因為這幾條replace into才能保證數據的一致性
4. 拷貝原表數據到臨時表中,在腳本中使用如下語句
INSERT IGNORE INTO $to_table ($columns) " "SELECT $columns FROM $from_table " "WHERE ($chunks->[$chunkno])",
我們能看到他是通過一些查詢(基本為主鍵、唯一鍵值)分批把數據導入到新的表中,在導入前,我們能通過參數--chunk-size對每次
導入行數進行控制,已減少對原表的鎖定時間,並且在導入時,我們能通過—sleep參數控制,在每個chunk導入后與下一次chunk導入開
始前sleep一會,sleep時間越長,對於磁盤IO的沖擊就越小
5. Rename 原表到old表中,在把臨時表Rename為原表
RENAME TABLE `$db`.`$tmp_tbl` TO `$db`.`$tbl` ;
在rename過程,其實我們還是會導致寫入讀取堵塞的,所以從嚴格意思上說,我們的OSC也不是對線上環境沒有一點影響,但由於
rename操作只是一個修改名字的過程,也只會修改一些表的信息,基本是瞬間結束,故對線上影響不太大
6. 清理以上過程中的不再使用的數據,如OLD表
以上即為整個Percona OSC的過程,我們看到精華部分就觸發器那一塊,不過還有很多細節我未介紹,如:外鍵、記錄binlog(默認情況是不記錄binlog的)等等
由於環境的復雜性,此工具還是有很多風險,如以下幾個方面問題或者需要規避的一些問題:
1. 此工具不是原子操作,如果某一點失敗,不僅僅會留下很多中間過程的垃圾文件,而這些文件很難完全清理,並且如果有這些文件存在,
那么就不能在次執行OSC操作;
2.在執行時,盡量避免有這個表的批量更新、鎖表、優化表的操作,我們能想象的到,如果有鎖表、優化表那么OSC是否還能正常執行?
3.如果存在主從結構,那么盡量在從庫先執行,因為如果在主庫執行完畢后在到從庫執行,我們能想象,主庫字段多同步到從庫,會不會有問題呢?
4.必須是單一列的主鍵或者單一唯一鍵,這樣我們在insert select *from分片時,是不是能更好的處理量呢?
5.不要有外鍵,盡管腳本經過嚴格測試,但是是否還有bug,也未知,表的外鍵是不是會帶來更多的問題呢?
6.在執行之前,我們是不是要對磁盤容量進行評估呢?因為OSC會使用表的一倍以上空間。
**以上列到的,只是部分問題,我想如果需要在線進行實施,還需要經過嚴格的測試,但是它的實現為我們提供了一個很好的在線更改表結構 方法,我相信只要我們能很好的規避他的弊端,它會給我們帶來很大的幫助;
**
PS:
使用 pt-osc 修改主鍵時注意:
原表上有個復合主鍵,現在要添加一個自增 id 作為主鍵,如何進行?
會涉及到以下修改動作:
1.刪除復合主鍵定義
2.添加新的自增主鍵 3.原復合主鍵字段,修改成唯一索引
需要將刪除原主鍵、增加新主鍵和增加原主鍵為唯一鍵同時操作:
alter "DROP PRIMARY KEY,add column pk int auto_increment primary key,add unique key uk_id_k(id,k)
3.4 OAK Openark – kit
openark kit 提供一組小程序,用來幫助日常的 MySQL 維護任務,可代替繁雜的手工操作。
oak-apply-ri: apply referential integrity on two columns with parent-child relationship.
oak-block-account: block or release MySQL users accounts, disabling them or enabling them to login.
oak-chunk-update: Perform long, non-blocking UPDATE/DELETE operation in auto managed small chunks.
oak-kill-slow-queries: terminate long running queries.
oak-modify-charset: change the character set (and collation) of a textual column.
oak-online-alter-table: Perform a non-blocking ALTER TABLE operation.
oak-purge-master-logs: purge master logs, depending on the state of replicating slaves.
oak-security-audit: audit accounts, passwords, privileges and other security settings.
oak-show-limits: show AUTO_INCREMENT “free space”.
oak-show-replication-status: show how far behind are replicating slaves on a given master.
示例
python oak-online-alter-table -u root --ask-pass -S /u01/mysql/my3306/run/mysql.sock -d replTestDB -t sbtest1 -g new_sbtest1 -a "add last_update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,add key last_update_time(last_update_time)" --sleep=300 --skip-delete-pass
實現細節
# 1.確認該表是否符合 oak-online-alter-table 的執行條件:
已有觸發器? 檢查觸發器->備份觸發器->刪除觸發器
SELECT TRIGGER_SCHEMA,TRIGGER_NAME,EVENT_OBJECT_SCHEMA,
EVENT_OBJECT_TABLE
FROM information_schema.TRIGGERS
WHERE event_object_schema = 'replTestDB';
Select * from information_schema.key_column_usage where
Referenced_table_schema='replTestDB' and
Referenced_table_name='sbtest1';
#2.執行 oak 命令,至於執行時間:
如果表有 1億,大概要執行 12 個小時,就需要在一周業務量少的時候執行
cd /u01/tools/openark-kit-196/scripts/
python oak-online-alter-table -u root --ask-pass -S /u01/mysql/my3306/run/mysql.sock -d replTestDB -t sbtest1 -g new_sbtest1 -a "add last_update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,add key last_update_time(last_update_time)" --sleep=300 --skip-delete-pass
#3.Online DDL 之后要進行數據一致性校驗:如果 DDL 改變了表字段類型,可能導致表數據變化
#4.表切換
rename table sbtest1 to old_sbtest1,new_sbtest1 to sbtest1;
#5. 刪除觸發器
drop trigger sbtest1_AI_oak;
drop trigger sbtest1_AU_oak;
drop trigger sbtest1_AD_oak;
#6.刪除表