mysql修改表結構出現唯一索引沖突


#########################

 

 原文檔地址:  https://cloud.tencent.com/developer/article/1520220

 原文檔地址:https://blog.csdn.net/finalkof1983/article/details/88355314

直接在主庫上alter或者pt-osc操作都會報錯

   每次報錯的value 都不一樣, 添加ALGORITHM=COPY 即可搞定:

> alter TABLE ad_glc add column `number` bigint(20) not null DEFAULT 0 COMMENT 'xxx量';

ERROR 1062 (23000): Duplicate entry '2020-12-02 00:00:00-12444' for key 'uk_billing'

 

MySQL add/drop字段時報主鍵沖突

問題現象

很多DBA朋友做ddl 變更比如添加、刪除字段時,一定概率上會遇到如下報錯:

Duplicate entry '7458421' for key 'PRIMARY'

錯誤提示是主鍵沖突,但是當我們去查詢 id= 7458421 時,並無此記錄。是不是很奇怪?遇到這種情況,一般有如下場景:

1  表具有一個或者多個唯一鍵。2 表比較大,執行DDL耗時超過數十秒。3 表的insert 操作比較頻繁。

問題分析

首先我們通過一個思維導圖了解一下MySQL online DDL 的過程,大家注意commit階段,會把ddl 執行期間的記錄的 log 重新應用到新的表上。

 

 

從官方文檔中的描述所說 online ddl 期間,其他會話執行的dml操作造成唯一鍵沖突的sql會記錄到 online log 中,在commit階段等變更結束之后再應用這些sql會導致報錯唯一鍵沖突。

When running an online DDL operation, the thread that runs the ALTER TABLE statement applies an online log of DML operations that were run concurrently on the same table from other connection threads. When the DML

operations are applied, it is possible to encounter a duplicate key entry error (ERROR 1062 (23000): Duplicate entry), even if the duplicate entry is only temporary and would be reverted by a later entry in the online log. This is similar to the idea of a foreign key constraint check in InnoDB in which constraints must hold during a transaction.

問題復現

構造一個2000w記錄的表,其表結構如下

CREATE TABLE `ddl` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `c1` int(10) NOT NULL DEFAULT '0', `c2` int(10) unsigned NOT NULL DEFAULT '0', `c3` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `c2` (`c2`), KEY `idx_c1` (`c1`)) ENGINE=InnoDB AUTO_INCREMENT=20000001 DEFAULT CHARSET=utf8mb4

舉一反三 ,其實只要是會導致重復記錄的sql語句,比如update,insert,insert into... on duplicate key,replace into 都會導致添加字段、刪除字段的ddl變更失敗。

如何解決呢 ,推薦使用 pt-osc或者 gh-ost 在線ddl變更工具

官方的討論

官方定對於該問題是online ddl的限制,有興趣的朋友可以閱讀下面兩個鏈接,了解官方和提交問題人員的討論記錄。

https://bugs.mysql.com/bug.php?id=76895

https://bugs.launchpad.net/percona-server/+bug/1445589

關聯知識

innodb_online_alter_log_max_size 是MySQL 5.6版本引入。該參數限定了online ddl操作時使用的臨時日志文件的最大大小。在創建索引或者對表進行alter操作時,該日志文件存儲了DDL操作期間對表的 insert,update,delete的數據記錄。臨時日志文件每次以

innodb_sort_buffer_size 為單位進行擴展直至達到 innodb_online_alter_log_max_size設置的最大值。如果臨時日志的大小超出規定限,則online ddl操作失敗,

當前所有未提交的DML操作會回滾。該參數設置日志文件太大帶來的負面影響是可能會導致DDL操作最后鎖定表(Waiting for table metadata lock)的時間更長,因為要花費更長的時間應用日志到表上。所以涉及到dml比較頻繁的表的ddl 盡量放到業務低峰操作。

 

 

 

基礎材料:

centos7.5  mysql 5.7.24


online DDL是在mysql5.6版本后加入的特性,用於支持DDL執行期間DML語句的並行操作,提高數據庫的吞吐量。

online DDL結構簡圖如下:

 

由上圖可知online DDL大體可以分為3部分:

1、copy(ALGORITHM=COPY)這部分是offline的,在DDL執行期間其他DML不能並行,也是5.6版本前的DDL執行方法。其間生成臨時表(server層的操作支持所有引擎),用於寫入原表修改過的數據,同時在原表路徑下會生成臨時表的.frm和.ibd文件。在innodb中不支持使用inplace的操作都會自動使用copy方式執行,而MyISAM表只能使用copy方式。

2、inplace(ALGORITHM=INPLACE)所有操作在innodb引擎層完成,不需要經過臨時表的中轉。除上圖兩種特殊索引創建外,其他以inplace方式執行的操作都是online的,執行期間其他DML操作可以並行,其中又以是否重建表又分為兩個部分rebuild和no-rebuild。

      rebuild部分涉及表的重建,在原表路徑下創建新的.frm和.ibd文件,消耗的IO會較多。期間(原表可以修改)會申請row log空間記錄DDL執行期間的DML操作,這部分操作會在DDL提交階段應用新的表空間中。

      no-rebuild部分由於不涉及表的重建,除創建添加索引,會產生部分二級索引的寫入操作外,其余操作均只修改元數據項,即只在原表路徑下產生.frm文件,不會申請row log,不會消耗過多的IO,速度通常很快。 

3、inplace but offline的幾種特殊DDL操作,本身是按inplace方式執行,但是執行期間DML語句卻不能並行。

注:如何區分DDL語句是使用了copy方式還是inplace方式,只需要查看語句執行完成輸出結果中的 X rows affected,如果X為0則是inplace(online)方式,如果不為0則是copy(offline)方式。


online DDL可選參數示意圖:

online DDL的兩個子選項包括ALGORITHM和LOCK:

對於ALGORITHM參數使用default默認值即可,不需要強制指定該值,系統會自行判斷,優先使用inplace,對於不支持的表或DDL操作使用copy。

LOCK參數絕大多數情況下也不需要顯式指定值,默認值default已經是盡可能允許DML的並行操作了。

例句如下,參數間使用逗號隔開:

alter table innodb_test add test int,ALGORITHM=INPLACE,LOCK=DEFAULT;


inplace(rebuild)的整體執行過程如下:

准備階段:

1、對表加元數據共享升級鎖,並升級為排他鎖。(此時DML不能並行)

2、在原表所在的路徑下創建.frm和.ibd臨時中轉文件(no-rebuild除創建二級索引外只創建.frm文件,其中添加二級索引操作最為特殊,該操作屬於no-rebuild不會生成.ibd,但實際上對.ibd文件卻做了修改,該操作會在參數tmpdir指定路徑下生成臨時文件,用於存儲索引排序結果,然后再合並到.ibd文件中)

3、申請row log空間,用於存放DDL執行階段產生的DML操作。(no-rebuild不需要)

執行階段:

1、釋放排他鎖,保留元數據共享升級鎖(此時DML可以並行)。

2、掃描原表主鍵以及二級索引的所有數據頁,生成 B+ 樹,存儲到臨時文件中;

3、將所有對原表的DML操作記錄在日志文件row log中

注:如果只修改元數據部分(no-rebuild)該階段只是修改.frm文件,不需要其他操作,也不需要申請row log

提交階段:

1、升級元數據共享升級鎖,產生排他鎖鎖表(此時DML不能並行)。

2、重做row log中的內容。(no-rebuild不需要)

3、重命名原表文件,將臨時文件改名為原表文件名,刪除原表文件

4、提交事務,變更完成。

說明:在DDL期間產生的數據,會按照正常操作一樣,寫入原表,記redolog、undolog、binlog,並同步到從庫去執行,只是額外會記錄在row log中,並且寫入row log的操作本身也會記錄redolog,而在提交階段才進行row log重做,此階段會鎖表,此時主庫(新表空間+row log)和從庫(表空間)數據是一致的,在主庫DDL操作執行完成並提交,這個DDL才會寫入binlog傳到從庫執行,在從庫執行該DDL時,這個DDL對於從庫本地來講仍然是online的,也就是在從庫本地直接寫入數據是不會阻塞的,也會像主庫一樣產生row log。但是對於主庫同步過來DML,此時會被阻塞,是offline的,DDL是排他鎖的在復制線程中也是一樣,所以不只會阻塞該表,而是后續所有從主庫同步過來的操作(主要是在復制線程並行時會排他,同一時間只有他自己在執行)。所以大表的DDL操作,會造成同步延遲。


 copy的整體執行過程如下:

1、鎖表,期間DML不可並行執行

2、生成臨時表以及臨時表文件(.frm .ibd)

3、拷貝原表數據到臨時表

4、重命名臨時表及文件

5、刪除原表及文件

6、提交事務,釋放鎖


online DDL的空間要求:

由於online DDL執行期間需要創建臨時表空間文件用於存儲數據,以及申請row log記錄DML操作,所以在執行DDL前應該先確認空間上是否滿足要求,否則由於空間不夠很可能導致操作失敗,而進行回滾。

1、row log空間:row log空間每次申請的大小由 innodb_sort_buffer_size決定,最大值由innodb_online_alter_log_max_size,該值默認為128M,支持動態修改。對於更新頻繁的表來講,如果預計在DDL期間對表的更新操作存儲可能超過128M時,需要為本次操作增大該值。當然如果不涉及rebuild操作時,不需要考慮該值。如果提示DB_ONLINE_LOG_TOO_BIG錯誤,則是由innodb_online_alter_log_max_size空間不足造成的。

2、索引排序空間:如果DDL操作涉及二級索引的創建,會在MySQL臨時目錄產生臨時排序文件,將中間的排序結果寫入文件,最終將內容合並到最終表或索引中,然后自動刪除臨時排序文件。這個路徑默認為mysql全局參數tmpdir指定(默認值為/tmp,如果手動指定了innodb_tmpdir參數的路徑,則tmpdir會被覆蓋),且不會在原始表的目錄中創建臨時排序文件。tmpdir需要保證能夠容納要創建的二級索引,臨時排序文件最大可能需要的空間等於表中的數據量加上索引,否則執行將報錯。(官方文檔的說明,實際測試200萬的表加索引,並未生成臨時排序文件,這有點奇怪)

3、中間表空間:如果DDL操作涉及rebuild表,則會在原表所在目錄創建臨時表空間文件(以#sql開頭),臨時表空間大小需要等於原表大小,重建完成后會自動重命名臨時表空間,刪除原表空間。所以執行rebuild操作時需要保證原表所在路徑下有足夠空間


執行DDL語句需要額外注意的是:

  • 如果操作失敗,執行回滾操作時可能會影響服務器性能。

  • 長時間運行的聯機DDL操作可能導致復制滯后。在從服務器上運行之前,聯機DDL操作必須在主服務器上完成運行。此外,在主服務器上同時處理的DML僅在從服務器上的DDL操作完成后才在從服務器上處理。

 

 

 

 

 

###################################


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM