Oracle與GreatSQL差異:更改唯一索引列
1.問題來源
在從Oracle遷移到GreatSQL的應用系統中,一條普通的update語句在GreatSQL中卻報錯,需要進行SQL語句的改寫。把實際問題簡化為下面簡單情況進行說明。
在Oracle下,可以正常執行的update語句。
-- 建表
CREATE TABLE test.test1 (
id INT PRIMARY KEY,
k INT NOT NULL,
c CHAR(120) NOT NULL,
pad CHAR(60) NOT NULL
);
-- 建立唯一索引
CREATE UNIQUE INDEX ui_test1_k ON test.test1 (k);
-- 插入資料
INSERT INTO test.test1 VALUES
(1, 1, 'cc', 'pad'),
(2, 2, 'cc', 'pad'),
(3, 3, 'cc', 'pad'),
(4, 4, 'cc', 'pad');
-- 執行 UPDATE 語句
UPDATE test.test1 SET k = k + 1;
UPDATE test.test1 SET k = k - 1;
在GreatSQL下準備測試表和資料:
CREATE TABLE `test1` (
`id` int NOT NULL AUTO_INCREMENT,
`k` int NOT NULL DEFAULT '0',
`c` char(120) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
`pad` char(60) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
UNIQUE KEY `ui_k` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
greatsql> INSERT INTO test1 VALUES (1, 1, 'cc', 'pad');
greatsql> INSERT INTO test1 VALUES (2, 2, 'cc', 'pad');
greatsql> INSERT INTO test1 VALUES (3, 3, 'cc', 'pad');
greatsql> INSERT INTO test1 VALUES (4, 4, 'cc', 'pad');
同樣的update語句,在GreatSQL下執行報錯:
greatsql> UPDATE test1 SET k = k + 1;
ERROR 1062 (23000): Duplicate entry '2' for key 'test1.ui_k'
在GreatSQL下,UPDATE語句為什麼報錯呢?使用什麼方法可以高效執行呢?
2.解決方法
由UPDATE語句的報錯可知是唯一鍵重複的問題,將k=1的行更改k=k+1=2,與k=2的行重複。解決方法有2個方向:
- 讓唯一索引暫時失效,update完成後再啟用
- 讓資料按一定的順序執行,避免資料重複
嘗試設定 unique_checks為0
設定會話系統變數unique_checks=0,則允許儲存引擎假定輸入資料中不存在重複的鍵。如果您確定您的資料不包含唯一性衝突,那麼您可以將它設定為0,以加快將大型表匯入InnoDB的速度。將此變數設定為0並不要求儲存引擎忽略重複的鍵。仍然允許引擎檢查它們,並且如果它檢測到它們,就發出重複索引的錯誤。
實際測試,設定UNIQUE_CHECKS=0,update語句仍然報錯。
greatsql> SET UNIQUE_CHECKS=0;
Query OK, 0 rows affected (0.00 sec)
greatsql> UPDATE test1 SET k = k + 1;
ERROR 1062 (23000): Duplicate entry '2' for key 'test1.ui_k'
方法1:刪除唯一索引,update後重建
刪除唯一索引,update後重建唯一索引的方法,有2個DDL操作,由於DDL前會自動提交事務,這種處理方法不能和其他操作在同一個事務中,同時也存在update後(有重複值)無法建立唯一索引的風險。適合資料的手工一次性處理。
greatsql> ALTER TABLE test1 DROP index kc;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
greatsql> UPDATE test1 SET k=k+1 ;
Query OK, 100 rows affected (0.01 sec)
Rows matched: 100 Changed: 100 Warnings: 0
greatesql> ALTER TABLE test1 ADD UNIQUE key ui_k(k);
Query OK, 0 rows affected (0.09 sec)
Records: 0 Duplicates: 0 Warnings: 0
方法2:update按順序執行
可以在update語句中使用order by子句,按照k值順序執行,避免update後的資料與原有資料重複。
#k=k+1時,需要按照降序執行
greatsql> UPDATE test1 SET k=k+1 ORDER BY k DESC;
Query OK, 4 rows affected (0.01 sec)
Rows matched: 4 Changed: 4 Warnings: 0
greatsql> UPDATE test1 SET k=k+1 ORDER BY k ;
ERROR 1062 (23000): Duplicate entry '2' for key 'test1.ui_k'
#k=k-1時,需要按照升序執行
greatsql> UPDATE test1 SET=k-1 ORDER BY k ;
Query OK, 4 rows affected (0.01 sec)
Rows matched: 4 Changed: 4 Warnings: 0
greatsql> UPDATE test1 SET k=k-1 ORDER BY k DESC;
ERROR 1062 (23000): Duplicate entry '4' for key 'test1.ui_k'
執行效率對比
對比方法1和方法2的執行效率。
#方法1:刪除唯一索引,UPDATE後重建
greatsql> ALTER TABLE test1 DROP index k;
Query OK, 0 rows affected (0.17 sec)
Records: 0 Duplicates: 0 Warnings: 0
greatsql> UPDATE test1 SET k=K+1;
Query OK, 1000000 rows affected (35.08 sec)
Rows matched: 1000000 Changed: 1000000 Warnings: 0
greatsql> ALTER TABLE test1 ADD UNIQUE index kc(k,c);
Query OK, 0 rows affected (12.35 sec)
Records: 0 Duplicates: 0 Warnings: 0
#方法2:UPDATE按順序執行
greatsql> UPDATE test1 set k=K+1 ORDER BY k DESC;
Query OK, 1000000 rows affected (1 min 36.81 sec)
Rows matched: 1000000 Changed: 1000000 Warnings: 0
總結:執行時間 方法1:方法2=47.50 : 96.81 = 1 : 2.04 ,方法1(刪除唯一索引,update後重建)比方法2(update按順序執行)執行行效率高1倍。
3.GreatSQL原始碼分析
透過對GreatSQL原始碼的分析,瞭解到上面update語句從server層呼叫InnoDB儲存引擎層函式的呼叫關係如下,每update 1行資料呼叫ha_update_row()一次,每update 1行資料後都要檢查唯一索引是否發生衝突。
#server層
Sql_cmd_update::update_single_table()
->ha_update_row() //更新一行資料
#innodb 儲存引擎層
-> ha_innobase::update_row() //更新innodb一行資料
-> row_update_for_GreatSQL() //修改或刪除資料
-> row_update_for_GreatSQL_using_upd_graph() //更新行
->row_upd_step() //更新行
->row_upd() //更新索引
->row_upd_sec_step() //更新索引
->row_ins_sec_index_entry() //向索引中插入記錄
->row_ins_sec_index_entry_low() //向索引中插入記錄
->row_ins_scan_sec_index_for_duplicate() //檢查索引重複值
->row_ins_dupl_error_with_rec() //檢查唯一索引衝突
handler::ha_update_row
函式的主要功能是更新表中的一行資料,並記錄該操作到二進位制日誌中。ha_innobase::update_row
函式的主要功能是更新InnoDB表中的一行資料。row_update_for_GreatSQL
修改或刪除資料行。row_update_for_GreatSQL_using_upd_graph
函式的主要功能是處理 GreatSQL 的行更新操作。row_upd_step
函式的主要功能是處理行更新操作。row_upd
函式是更改資料行影響的索引。row_upd_sec_step
函式是根據記錄行的更改或刪除,更改二級索引或刪除二級索引。row_ins_sec_index_entry
函式的主要功能是向二級索引中插入一條記錄。row_ins_sec_index_entry_low
函式的主要功能是向二級索引中插入一個索引項。它首先進行一些初始化和檢查,然後根據索引型別(空間索引或普通索引)進行搜尋。在搜尋過程中,它會檢查唯一性約束,並根據需要執行插入或修改操作。row_ins_scan_sec_index_for_duplicate
函式的主要功能是掃描非聚集唯一索引,以檢查是否存在與要插入的索引條目重複的記錄。row_ins_dupl_error_with_rec
函式的主要功能是檢查在插入索引條目時是否會發生唯一鍵衝突。它透過比較要插入的條目和現有記錄的欄位來確定是否存在重複。
4.總結
在更改唯一索引列時,Oracle是完成SQL語句全部資料的更改後,再檢查唯一索引的衝突;GreatSQL則是在SQL語句更改每1條資料後,在更新索引資料檢查唯一索引的衝突。在應用系統從Oracle遷移到GreatSQL時,需注意予以改寫。
5.延伸閱讀
- UPDATE 時主鍵衝突引發的思考(https://imysql.com/2008_06_17_sth_about_update_duplicate_key)
Enjoy GreatSQL 😃
關於 GreatSQL
GreatSQL是適用於金融級應用的國內自主開源資料庫,具備高效能、高可靠、高易用性、高安全等多個核心特性,可以作為MySQL或Percona Server的可選替換,用於線上生產環境,且完全免費併相容MySQL或Percona Server。
相關連結: GreatSQL社群 Gitee GitHub Bilibili
GreatSQL社群:
社群部落格有獎徵稿詳情:https://greatsql.cn/thread-100-1-1.html
技術交流群:
微信:掃碼新增
GreatSQL社群助手
微信好友,傳送驗證資訊加群
。