MySQL唯 一鍵約束場景下卻能插入重複行?
1 、故障描述
某次,使用者的一套 MySQL從庫在同步時出現寫入衝突報錯。透過檢查從庫的錯誤日誌資訊,可以很清晰的發現報錯共有兩處,先是寫入衝突報錯,使用者選擇跳過該衝突事務之後,又碰到了刪除報錯。透過檢視從庫的binlog日誌可以發現從庫並不存在寫入的重複行資料,那從庫的第一個報錯又從何而來呢。
2022-07-07 05:39:22 154606 [ERROR] Slave SQL: Could not execute Write_rows event on table zntj_gg.t_ius_user_sub_service; Duplicate entry '03254A7657C525984A2BB-aaa_001_86_3_5_1_1_gcw2-fitness' for key 'std_id', Error_code: 1062; handler error HA_ERR_FOUND_DUPP_KEY; the event's master log mysql-bin.004166, end_log_pos 255398155, Error_code: 1062 2022-07-07 05:39:22 154606 [Warning] Slave: Duplicate entry '03254A7657C525984A2BB-aaa_001_86_3_5_1_1_gcw2-fitness' for key 'std_id' Error_code: 1062 2022-07-07 05:39:22 154606 [ERROR] Error running query, slave SQL thread aborted. Fix the problem, and restart the slave SQL thread with "SLAVE START". We stopped at log 'mysql-bin.004166' position 255397852 2022-07-07 09:27:32 154606 [ERROR] Error reading packet from server: Lost connection to MySQL server during query ( server_errno=2013) 2022-07-07 09:27:32 154606 [Note] Slave I/O thread killed while reading event 2022-07-07 09:27:32 154606 [Note] Slave I/O thread exiting, read up to log 'mysql-bin.004167', position 58828583 2022-07-07 09:29:30 154606 [Warning] Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information. 2022-07-07 09:29:30 154606 [Warning] Slave SQL: If a crash happens this configuration does not guarantee that the relay log info will be consistent, Error_code: 0 2022-07-07 09:29:30 154606 [Note] Slave SQL thread initialized, starting replication in log 'mysql-bin.004166' at position 255397852, relay log './mysqld-relay-bin.006839' position: 255398015 2022-07-07 09:29:30 154606 [Note] 'SQL_SLAVE_SKIP_COUNTER=1' executed at relay_log_file='./mysqld-relay-bin.006839', relay_log_pos='255398015', master_log_name='mysql-bin.004166', master_log_pos='255397852' and new position at relay_log_file='./mysqld-relay-bin.006839', relay_log_pos='255398349', master_log_name='mysql-bin.004166', master_log_pos='255398186' 2022-07-07 09:29:30 154606 [Note] Slave I/O thread: connected to master 'replicauser@125.210.102.17:3306',replication started in log 'mysql-bin.004167' at position 58828583 2022-07-07 09:29:30 154606 [ERROR] Slave SQL: Could not execute Delete_rows event on table zntj_gg.t_ius_user_sub_service; Can't find record in 't_ius_user_sub_service', Error_code: 1032; handler error HA_ERR_KEY_NOT_FOUND; the event's master log mysql-bin.004166, end_log_pos 255398594, Error_code: 1032 2022-07-07 09:29:30 154606 [Warning] Slave: Can't find record in 't_ius_user_sub_service' Error_code: 1032 2022-07-07 09:29:30 154606 [ERROR] Error running query, slave SQL thread aborted. Fix the problem, and restart the slave SQL thread with "SLAVE START". We stopped at log 'mysql-bin.004166' position 255398186 2022-07-07 09:36:27 154606 [ERROR] Error reading packet from server: Lost connection to MySQL server during query ( server_errno=2013) 2022-07-07 09:36:27 154606 [Note] Slave I/O thread killed while reading event 2022-07-07 09:36:27 154606 [Note] Slave I/O thread exiting, read up to log 'mysql-bin.004167', position 84764060 2022-07-07 09:37:05 154606 [Warning] Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information. 2022-07-07 09:37:05 154606 [Note] Slave I/O thread: connected to master 'replicauser@125.210.102.17:3306',replication started in log 'mysql-bin.004167' at position 84764060 2022-07-07 09:37:05 154606 [Warning] Slave SQL: If a crash happens this configuration does not guarantee that the relay log info will be consistent, Error_code: 0 2022-07-07 09:37:05 154606 [Note] Slave SQL thread initialized, starting replication in log 'mysql-bin.004166' at position 255398186, relay log './mysqld-relay-bin.006839' position: 255398349 2022-07-07 09:37:05 154606 [Note] 'SQL_SLAVE_SKIP_COUNTER=1' executed at relay_log_file='./mysqld-relay-bin.006839', relay_log_pos='255398349', master_log_name='mysql-bin.004166', master_log_pos='255398186' and new position at relay_log_file='./mysqld-relay-bin.006839', relay_log_pos='255398788', master_log_name='mysql-bin.004166', master_log_pos='255398625'
2 、根因分析
透過報錯資訊,檢查相關表建表語句,懷疑當時從庫是因為唯 一鍵衝突進而產生報錯,然後使用者選擇使用 SQL_SLAVE_SKIP_COUNTER跳過該事務,使該資料未能寫入,導致後面的delete事務無法找到匹配的行。
| t_ius_user_sub_service | CREATE TABLE `t_ius_user_sub_service` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `createTime` datetime DEFAULT NULL, `lastUpdateTime` datetime DEFAULT NULL, `status` bigint(20) DEFAULT NULL, `cust_code` varchar(255) DEFAULT NULL, `std_id` varchar(255) DEFAULT NULL, `sub_service_code` varchar(255) DEFAULT NULL, `serviceType` varchar(255) DEFAULT NULL, `watchnum` int(11) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `std_id` (`std_id`,`sub_service_code`,`serviceType`) USING BTREE, KEY `sub_service_code` (`sub_service_code`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=2913132 DEFAULT CHARSET=utf8
很快,透過解析主庫的 binlog日誌,得到了確實的證據,在主庫的binlog日誌中,可以看到先是插入了兩條語句,然後透過delete刪除了這兩條語句。那麼問題來了,這兩條插入的語句,根據建表語句中的設定,應該是會產生唯 一鍵衝突的,為什麼從庫執行失敗報錯了,主庫卻能順利執行呢?
# at 255397487 #220707 5:39:22 server id 10261 end_log_pos 255397518 CRC32 0xea016589 Xid = 270516762358 COMMIT/*!*/; # at 255397518 #220707 5:39:22 server id 10261 end_log_pos 255397593 CRC32 0x6f5eeff0 Query thread_id=172971976 exec_time=0 error_code=0 SET TIMESTAMP=1657143562/*!*/; BEGIN /*!*/; # at 255397593 #220707 5:39:22 server id 10261 end_log_pos 255397680 CRC32 0x40ae4e57 Table_map: `zntj_gg`.`t_ius_user_sub_service` mapped to number 270345775 # at 255397680 #220707 5:39:22 server id 10261 end_log_pos 255397821 CRC32 0x8658dd38 Write_rows: table id 270345775 flags: STMT_END_F ### INSERT INTO `zntj_gg`.`t_ius_user_sub_service` ### SET ### @1=2912517 ### @2='2022-07-07 05:39:18' ### @3='2022-07-07 05:39:18' ### @4=0 ### @5='33040120001443' ### @6='03254A7657C525984A2BB' ### @7='aaa_001_86_3_5_1_1_gcw2' ### @8='fitness' ### @9=0 # at 255397821 #220707 5:39:22 server id 10261 end_log_pos 255397852 CRC32 0x78e79f44 Xid = 270516762129 COMMIT/*!*/; # at 255397852 #220707 5:39:22 server id 10261 end_log_pos 255397927 CRC32 0x7833f5aa Query thread_id=172587714 exec_time=0 error_code=0 SET TIMESTAMP=1657143562/*!*/; BEGIN /*!*/; # at 255397927 #220707 5:39:22 server id 10261 end_log_pos 255398014 CRC32 0xba4235b0 Table_map: `zntj_gg`.`t_ius_user_sub_service` mapped to number 270345775 # at 255398014 #220707 5:39:22 server id 10261 end_log_pos 255398155 CRC32 0xe772beb9 Write_rows: table id 270345775 flags: STMT_END_F ### INSERT INTO `zntj_gg`.`t_ius_user_sub_service` ### SET ### @1=2912518 ### @2='2022-07-07 05:38:38' ### @3='2022-07-07 05:38:38' ### @4=0 ### @5='33040120001443' ### @6='03254A7657C525984A2BB' ### @7='aaa_001_86_3_5_1_1_gcw2' ### @8='fitness' ### @9=0 # at 255398155 #220707 5:39:22 server id 10261 end_log_pos 255398186 CRC32 0x1e02a19d Xid = 270516762223 COMMIT/*!*/; # at 255398186 #220707 5:36:36 server id 10261 end_log_pos 255398261 CRC32 0x0080cbe2 Query thread_id=149724384 exec_time=166 error_code=0 SET TIMESTAMP=1657143396/*!*/; BEGIN /*!*/; # at 255398261 #220707 5:36:36 server id 10261 end_log_pos 255398348 CRC32 0xb2c4244c Table_map: `zntj_gg`.`t_ius_user_sub_service` mapped to number 270345775 # at 255398348 #220707 5:36:36 server id 10261 end_log_pos 255398594 CRC32 0xc964c586 Delete_rows: table id 270345775 flags: STMT_END_F ### DELETE FROM `zntj_gg`.`t_ius_user_sub_service` ### WHERE ### @1=2912517 ### @2='2022-07-07 05:39:18' ### @3='2022-07-07 05:39:18' ### @4=0 ### @5='33040120001443' ### @6='03254A7657C525984A2BB' ### @7='aaa_001_86_3_5_1_1_gcw2' ### @8='fitness' ### @9=0 ### DELETE FROM `zntj_gg`.`t_ius_user_sub_service` ### WHERE ### @1=2912518 ### @2='2022-07-07 05:38:38' ### @3='2022-07-07 05:38:38' ### @4=0 ### @5='33040120001443' ### @6='03254A7657C525984A2BB' ### @7='aaa_001_86_3_5_1_1_gcw2' ### @8='fitness' ### @9=0 # at 255398594 #220707 5:39:22 server id 10261 end_log_pos 255398625 CRC32 0xd01c4287 Xid = 270512016580 COMMIT/*!*/;
排查到這一步,不覺的陷入了沉思,在唯 一鍵約束存在的前提下,居然能插入重複行???難道,該約束被禁用了?首先,嘗試在常規情況下主庫能插入兩條唯 一鍵衝突資料的辦法。方式 1:透過設定unique_checks引數為0來關閉資料庫的唯 一鍵檢查。方式2:可以修改插入語句,透過insert ignore、replace into、insert on duplicate key update三種手段來規避唯 一鍵衝突。然而,實際情況unique_checks引數為1,且業務執行語句時並未修改unique_checks引數。而透過binlog日誌的記錄可以發現,當時確實是有兩個資料插入成功,與insert ignore、replace into、insert on duplicate key update這些手段的規避結果不同。
分析到這裡還沒有確切的證據,我們便需要模擬環境,嘗試對報錯的場景進行復現,透過分析主庫的 binlog日誌,我們可以發現,delete事務是在5:36:36開始執行,然後和兩個insert事務一起在5:39:22結束。模擬實際環境資料庫的版本和配置檔案,建立一個和原庫配置相同的資料庫來進行場景復現。
然後開啟四個視窗,第一個視窗加持鎖,然後第二個視窗對錶進行 delete操作,然後第三個視窗和第四個視窗準備好insert語句,最後根據binlog記錄依次執行。先執行insert語句,然後解鎖執行delete語句,儘量保持在較短時間內執行,以模擬場景中的同時進行。
最後透過幾輪測試會發現,模擬的結果與實際報錯場景不同,在測試中,插入唯 一鍵衝突的資料會報錯。說明實際場景中出現的情況是不合邏輯的,不常見的。
此時,相信很多朋友會納悶,那是不是碰到 MySQL BUG 了。懷揣著同樣的想法,筆者去 MySQL BUG 網站撈了一把,幸運的發現了新的收穫,確實存在類似的 B UG ,如下:
詳情可以參考:
3 、解決方案
為了防止業務表上出現不必要的重複行,或者避免資料錯亂,建議將當前 MySQL升級至當前系列的最新小版本或直接升級到高版本。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/30310891/viewspace-2939153/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- mysql避免插入重複資料MySql
- 主鍵約束、唯一約束和唯一索引索引
- 教你mysql如何增加外來鍵約束MySql
- Javaweb-約束-外來鍵約束JavaWeb
- MySQL 約束MySql
- Mysql-基本練習(06-唯一約束、外來鍵約束、新增、刪除單列)MySql
- MySQL防止重複插入相同記錄 insert if not existsMySql
- laravel 批次插入並在遇到重複鍵時更新Laravel
- 在 MotionScene 檔案中定義場景約束
- 故障案例:MySQL唯一索引有重複值,官方卻說This is not a bugMySql索引
- mysql不能新增外來鍵約束怎麼辦MySql
- 外來鍵約束
- MySql避免重複插入記錄的幾種方法MySql
- Mysql入門【Mysql約束】MySql
- 一句話從 MySQL 取出重複行MySql
- MySQL 欄位約束MySql
- MySQL自增約束MySql
- mysql~資料完整性考慮~外來鍵約束MySql
- mybatis oracle資料庫批次插入資料,忽略主鍵重複MyBatisOracle資料庫
- mysql資料庫約束MySql資料庫
- MySQL——約束(constraint)詳解MySqlAI
- 約束外來鍵筆記筆記
- 關於外來鍵約束
- 【SQL】15 SQL 約束(Constraints)、NOT NULL 約束、UNIQUE 約束、PRIMARY KEY 約束、FOREIGN KEY 約束、CHECK 約束、DEFAULT約束SQLAINull
- 事務插入和唯一性約束,後插入者是報錯還是阻塞?
- MySQL之完整性約束MySql
- 【MySQL】MySQL進階(外來鍵約束、多表查詢、檢視、備份與恢復)MySql
- 主鍵可以重複?
- 專案管理的三重約束專案管理
- AppBoxFuture(七): 分散式外來鍵約束APP分散式
- oracle外來鍵約束的總結Oracle
- mysql新增約束語句筆記MySql筆記
- ORACLE 在缺少主鍵ID的情況下刪除重複資料,只保留一行Oracle
- python 如何一次隨機取出多條資料(能重複/不能重複)Python隨機
- 批量刪除MSSQL 中主外來鍵約束SQL
- 5_MySQL 表的欄位約束MySql
- MySQL 中的約束及相關操作MySql
- MySQL學習筆記——建立與約束MySql筆記