1. 程式人生 > 資料庫 >mysql修改表結構出現唯一索引衝突

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

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

 

 原文件地址:

 原文件地址:

直接在主庫上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 操作比較頻繁。

問題分析

首先我們通過一個思維導圖瞭解一下 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或者

官方的討論

官方定對於該問題是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決定,最大值由,該值預設為128M,支援動態修改。對於更新頻繁的表來講,如果預計在DDL期間對錶的更新操作儲存可能超過128M時,需要為本次操作增大該值。當然如果不涉及rebuild操作時,不需要考慮該值。如果提示DB_ONLINE_LOG_TOO_BIG錯誤,則是由空間不足造成的。

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

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


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

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

  • 長時間執行的聯機DDL操作可能導致複製滯後。在從伺服器上執行之前,聯機DDL操作必須在主伺服器上完成執行。此外,在主伺服器上同時處理的DML僅在從伺服器上的DDL操作完成後才在從伺服器上處理。

 

 

 

 

 

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