什么是pt-online-schema-change,針對這個(gè)問(wèn)題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個(gè)問(wèn)題的小伙伴找到更簡(jiǎn)單易行的方法。
首先要說(shuō)明pt-online-schema-change工具并不是說(shuō)修改表結構的時(shí)候不上鎖,通常我們說(shuō)的鎖一般包含innodb 行鎖和MDL lock。而pt-online-schema-change工具就是將某些使用COPY算法的DDL操作使用DML操作來(lái)代替,換句話(huà)說(shuō)就是使用Innodb row鎖來(lái)代替MDL lock,因為原生的COPY算法的DDL會(huì )在MDL lock SNW這個(gè)類(lèi)型保護下完整個(gè)表復制操作,整個(gè)復制過(guò)程中是不允許DML操作,因此造成了我們COPY算法的DDL堵塞線(xiàn)程正常的現象,當然哪些DDL可以online進(jìn)行可以參考官方文檔online ddl一節。整個(gè)pt-online-schema-change工具修改過(guò)程中,只會(huì )在rename階段才會(huì )上MDL LOCK的X鎖,但是rename操作一般非??焖?。
我們大概看一下pt-online-schema-change的工作方式,這個(gè)實(shí)際上開(kāi)啟genrnal log就能看出來(lái)下面是重點(diǎn)步驟(我的表名叫做testpt_osc):
首先定義出新表
CREATE TABLE
test
._testpt_osc_new
(
id
int(11) NOT NULL,
name
varchar(20) DEFAULT NULL,
PRIMARY KEY (id
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
ALTER TABLEtest
._testpt_osc_new
add index name_index(name)
定義三個(gè)觸發(fā)器
delete 觸發(fā)器:
CREATE TRIGGERpt_osc_test_testpt_osc_del
AFTER DELETE ONtest
.testpt_osc
FOR EACH ROW
DELETE IGNORE FROMtest
._testpt_osc_new
WHEREtest
._testpt_osc_new
.id
<=> OLD.id
update 觸發(fā)器:
CREATE TRIGGERpt_osc_test_testpt_osc_upd
AFTER UPDATE ONtest
.testpt_osc
FOR EACH ROW
BEGIN
DELETE IGNORE FROMtest
._testpt_osc_new
WHERE !(OLD.id
<=> NEW.id
)
ANDtest
._testpt_osc_new
.id
<=> OLD.id
;
REPLACE INTOtest
._testpt_osc_new
(id
,name
) VALUES
(NEW.id
, NEW.name
);
ENDinsert 觸發(fā)器:
CREATE TRIGGERpt_osc_test_testpt_osc_ins
AFTER INSERT ONtest
.testpt_osc
FOR EACH ROW
REPLACE INTOtest
._testpt_osc_new
(id
,name
) VALUES
(NEW.id
, NEW.name
)
使用分塊(chunk)拷貝的方式
首先需要插入數據的確認上界:
SELECT /!40001 SQL_NO_CACHE /id
FROMtest
.testpt_osc
FORCE INDEX(PRIMARY
)
WHERE ((id
>= ‘1’)) ORDERBYid
LIMIT 1999, 2 /next chunk boundary/然后插入:
INSERT LOW_PRIORITY IGNORE INTOtest
._testpt_osc_new
(id
,name
)
SELECTid
,name
FROMtest
.testpt_osc
FORCE INDEX(PRIMARY
)
WHERE ((id
>= ‘1’)) AND ((id
<= ‘2000’)) LOCK IN SHARE MODE
最終進(jìn)行表的重新命名
使用RENAME TABLE
test
.tp1
TOtest
._tp1_old
,test
._tp1_new
TOtest
.tp1
進(jìn)程重新命名。
從整個(gè)過(guò)程來(lái)講需要注意的幾個(gè)地方:
對于delete和update觸發(fā)器來(lái)講,delete數據均使用了IGNORE進(jìn)行修飾,因此即便數據還沒(méi)有拷貝到新表也不會(huì )引發(fā)錯誤。
對于update和insert觸發(fā)器來(lái)講,均使用了replace這種操作來(lái)進(jìn)行,因此如果數據還沒(méi)有拷貝到新表那么將插入到新表中,如果數據已經(jīng)拷貝到新表那么將會(huì )修改其中的值。因此新表中總是保留的最新的數據。
對于分塊拷貝數據而言,使用是insert ignore 新表 select 老表 LOCK S 的方式,因此對于觸發(fā)器插入的最新值,是不會(huì )進(jìn)行修改的也不會(huì )報錯。打個(gè)比方chunk為200 當前拷貝數據到了1000行,但是我們手動(dòng)修改了第2000行的數據,那么第2000行將會(huì )在update觸發(fā)器的作用下提前插入到新表中,當拷貝數據來(lái)到這一行的時(shí)候因為使用了ignore則不會(huì )重復行的錯誤,并且數據是最新的。其次每次insert select操作是一個(gè)單獨的事務(wù)。
insert ignore 新表 select 老表 LOCK S 的方式 操作存在對新表中加自增鎖的可能,這取決于你的參數設置。
對于觸發(fā)器而言,原始語(yǔ)句和觸發(fā)語(yǔ)句被包裹在一個(gè)事務(wù)里面,也就是說(shuō)對于任何一個(gè)DML語(yǔ)句而言,修改老表和新表的數據需要的行鎖將會(huì )在一個(gè)事務(wù)中存在。
pt-online-schema-change 生成的binlog和redo都會(huì )比online DDL大得多,效率上講應該低于online DDL。
由于replace操作的存在,因此pt-online-schema-change將會(huì )依賴(lài)主鍵或者唯一鍵,否則將不能工作。
我們可以看到整個(gè)過(guò)程中有如下的重點(diǎn)知識點(diǎn):
觸發(fā)器和事務(wù)
Insert ignore/replace語(yǔ)法
自增死鎖的發(fā)生
其次對于第4和第5點(diǎn)來(lái)講,有出現死鎖的可能。下面我們分別討論。
在pt-online-schema-change中,觸發(fā)器占據了重要的地位,我們需要了解一下觸發(fā)器和事務(wù)之間的關(guān)系。我們常用的觸發(fā)器包含了before和after觸發(fā)器,代表著(zhù)對原表進(jìn)行DML操作前或者后進(jìn)行其它的操作,下面是我定義的兩個(gè)測試的觸發(fā)器如下:
CREATE TRIGGER testbef BEFORE INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO t2 values(new.id); END; CREATE TRIGGER testaft after INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO t3 values(new.id); END;
顯然如果對t1表進(jìn)行數據插入,那么會(huì )在之前向t2表插入一條數據,然后在之后向t3插入一條數據,這一點(diǎn)可以通過(guò)函數調用trace進(jìn)行驗證如下:
[root@ora12ctest mysql]# cat -n tri2.trace |grep row_ins 970 T@3: | | | | | | | | | | | >row_ins 971 T@3: | | | | | | | | | | | | row_ins: table: test/t2 向t2表插入數據 ... 1406 T@3: | | | | | | | | >row_ins 1407 T@3: | | | | | | | | | row_ins: table: test/t1 向t1表插入數據 ... 1779 T@3: | | | | | | | | | | | | >row_ins 1780 T@3: | | | | | | | | | | | | | row_ins: table: test/t3 向t3表插入數據 ...
這里就能夠看到順序了,其次我們還需要知道這些所有的操作會(huì )包裹在一個(gè)事務(wù)里面,這一點(diǎn)也可以通過(guò)函數調用trace進(jìn)行驗證,還可以使用binlog進(jìn)行驗證,下面是一次調用的binlog信息:
# at 194 (這里是GTID EVENT事務(wù)開(kāi)始) #200212 17:23:16 server id 1903313 end_log_pos 259 CRC32 0x4ff6735e GTID last_committed=0 sequence_number=1 rbr_only=yes /*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/; SET @@SESSION.GTID_NEXT= '92008a52-4b7d-11ea-9ec6-000c29c8aca8:202'/*!*/; # at 259 #200212 17:23:16 server id 1903313 end_log_pos 331 CRC32 0x1ebd3446 Query thread_id=3 exec_time=0 error_code=0 ... BEGIN /*!*/; # at 331 #200212 17:23:16 server id 1903313 end_log_pos 384 CRC32 0xe748dc3a Rows_query # INSERT INTO t2 values(new.id) # at 384 #200212 17:23:16 server id 1903313 end_log_pos 429 CRC32 0x093c5fe3 Table_map: `test`.`t1` mapped to number 108 # at 429 #200212 17:23:16 server id 1903313 end_log_pos 474 CRC32 0x92691238 Table_map: `test`.`t2` mapped to number 110 # at 474 #200212 17:23:16 server id 1903313 end_log_pos 519 CRC32 0x5b9a710f Table_map: `test`.`t3` mapped to number 111 # at 519 #200212 17:23:16 server id 1903313 end_log_pos 559 CRC32 0xe41b1119 Write_rows: table id 110 # at 559 #200212 17:23:16 server id 1903313 end_log_pos 599 CRC32 0x36c3511c Write_rows: table id 108 # at 599 #200212 17:23:16 server id 1903313 end_log_pos 639 CRC32 0xa68b9ae6 Write_rows: table id 111 flags: STMT_END_F ### INSERT INTO `test`.`t2` ### SET ### @1=11000 /* INT meta=0 nullable=0 is_null=0 */ ### INSERT INTO `test`.`t1` ### SET ### @1=11000 /* INT meta=0 nullable=1 is_null=0 */ ### INSERT INTO `test`.`t3` ### SET ### @1=11000 /* INT meta=0 nullable=0 is_null=0 */ # at 639 (這里是XID EVENT事務(wù)提交) #200212 17:23:16 server id 1903313 end_log_pos 670 CRC32 0xbbb6547b Xid = 19 COMMIT/*!*/;
這里我們使用binlog不僅驗證了執行順序并且還驗證了所有操作都包含在一個(gè)事務(wù)里面。既然所有的語(yǔ)句都包裹在一個(gè)事務(wù)里面,那么加鎖的范圍就更大了,這不僅關(guān)系到本身的DML操作表,并且還關(guān)系到觸發(fā)語(yǔ)句的相關(guān)表,需要額外注意。
其次所有語(yǔ)句不僅包裹在一個(gè)事務(wù)里面,并且共享一個(gè)錯誤返回接口,那么如下的錯誤:
mysql> select count(*) from t1; +----------+ | count(*) | +----------+ | 0 | +----------+ 1 row in set (0.00 sec) mysql> insert into t1 values(1); ERROR 1062 (23000): Duplicate entry '1' for key 'PRIMARY' mysql>
我們驚訝的發(fā)現t1表一條數據都沒(méi)有,但是居然返回重復的行。原因就在于雖然t1表沒(méi)有數據,但是t2或者t3表有違反唯一性檢查的可能,因此返回了錯誤,錯誤由統一的接口返回給客戶(hù)端。
最后觸發(fā)器會(huì )導致處理邏輯混亂,盡量避免使用觸發(fā)器。
關(guān)于ignore語(yǔ)法我們以insert ignore語(yǔ)法為例,一般來(lái)講如果遇到重復行insert ignore語(yǔ)法會(huì )通過(guò)忽略重復值錯誤的方式進(jìn)行跳過(guò),這實(shí)際上和replace的處理方式一致,但是replace不同的是如果遇到重復行不是進(jìn)行忽略,而是執行的delete然后執行insert操作。換句話(huà)說(shuō)他們的觸發(fā)形式一致,但是觸發(fā)后執行的行為是不同的,下面我們就來(lái)看看。
首先對于insert語(yǔ)句來(lái)講我們需要定位到需要插入的位置,這部分略過(guò)。
這一步對于主鍵/唯一索引 而言需要判斷是否已經(jīng)有重復的行。其判斷標準基本都是通過(guò)插入的值進(jìn)行索引定位,然后判斷定位游標的值是否和需要插入值相同,下面是棧幀:
主鍵:
#0 row_ins_duplicate_error_in_clust (flags=0, cursor=0x7fffec4347d0, entry=0x7367c00, thr=0x7362980, mtr=0x7fffec433fa0) at /mysql/mysql-5.7.26/storage/innobase/row/row0ins.cc:2273 #1 0x0000000001ae47d4 in row_ins_clust_index_entry_low (flags=0, mode=2, index=0x73674f0, n_uniq=1, entry=0x7367c00, n_ext=0, thr=0x7362980, dup_chk_only=false) at /mysql/mysql-5.7.26/storage/innobase/row/row0ins.cc:2555 #2 0x0000000001ae697a in row_ins_clust_index_entry (index=0x73674f0, entry=0x7367c00, thr=0x7362980, n_ext=0, dup_chk_only=false) at /mysql/mysql-5.7.26/storage/innobase/row/row0ins.cc:3293 #3 0x0000000001ae6e88 in row_ins_index_entry (index=0x73674f0, entry=0x7367c00, thr=0x7362980) at /mysql/mysql-5.7.26/storage/innobase/row/row0ins.cc:3429 #4 0x0000000001ae73e2 in row_ins_index_entry_step (node=0x7362710, thr=0x7362980) at /mysql/mysql-5.7.26/storage/innobase/row/row0ins.cc:3579 #5 0x0000000001ae7749 in row_ins (node=0x7362710, thr=0x7362980) at /mysql/mysql-5.7.26/storage/innobase/row/row0ins.cc:3717 #6 0x0000000001ae7bae in row_ins_step (thr=0x7362980) at /mysql/mysql-5.7.26/storage/innobase/row/row0ins.cc:3853 #7 0x0000000001b05f73 in row_insert_for_mysql_using_ins_graph (mysql_rec=0x736e7a0 "\375\002", prebuilt=0x7362170) at /mysql/mysql-5.7.26/storage/innobase/row/row0mysql.cc:1738 #8 0x0000000001b06484 in row_insert_for_mysql (mysql_rec=0x736e7a0 "\375\002", prebuilt=0x7362170) at /mysql/mysql-5.7.26/storage/innobase/row/row0mysql.cc:1859 #9 0x00000000019adca9 in ha_innobase::write_row (this=0x736e4b0, record=0x736e7a0 "\375\002") at /mysql/mysql-5.7.26/storage/innobase/handler/ha_innodb.cc:7598 #10 0x0000000000f67dc0 in handler::ha_write_row (this=0x736e4b0, buf=0x736e7a0 "\375\002") at /mysql/mysql-5.7.26/sql/handler.cc:8062 #11 0x00000000017cf173 in write_record (thd=0x6dd5660, table=0x735afa0, info=0x7fffec435b50, update=0x7fffec435ad0) at /mysql/mysql-5.7.26/sql/sql_insert.cc:1873 #12 0x00000000017cc24a in Sql_cmd_insert::mysql_insert (this=0x6debbc8, thd=0x6dd5660, table_list=0x6deb638) at /mysql/mysql-5.7.26/sql/sql_insert.cc:769 #13 0x00000000017d2d7f in Sql_cmd_insert::execute (this=0x6debbc8, thd=0x6dd5660) at /mysql/mysql-5.7.26/sql/sql_insert.cc:3118 #14 0x000000000159a70c in mysql_execute_command (thd=0x6dd5660, first_level=true) at /mysql/mysql-5.7.26/sql/sql_parse.cc:3596 #15 0x00000000015a06c0 in mysql_parse (thd=0x6dd5660, parser_state=0x7fffec437610) at /mysql/mysql-5.7.26/sql/sql_parse.cc:5570 #16 0x0000000001595283 in dispatch_command (thd=0x6dd5660, com_data=0x7fffec437d80, command=COM_QUERY) at /mysql/mysql-5.7.26/sql/sql_parse.cc:1484 #17 0x00000000015940bc in do_command (thd=0x6dd5660) at /mysql/mysql-5.7.26/sql/sql_parse.cc:1025 #18 0x00000000016cbf91 in handle_connection (arg=0x6dda360) at /mysql/mysql-5.7.26/sql/conn_handler/connection_handler_per_thread.cc:306 #19 0x0000000001921c64 in pfs_spawn_thread (arg=0x6d7d0f0) at /mysql/mysql-5.7.26/storage/perfschema/pfs.cc:2190 #20 0x0000003823e079d1 in start_thread () from /lib64/libpthread.so.0 #21 0x0000003823ae8b6d in clone () from /lib64/libc.so.6二級索引唯一鍵
#0 row_ins_scan_sec_index_for_duplicate (flags=0, index=0x7366e10, entry=0x7367ca8, thr=0x7362980, s_latch=false, mtr=0x7fffec434020, offsets_heap=0x7389038) at /mysql/mysql-5.7.26/storage/innobase/row/row0ins.cc:2050 #1 0x0000000001ae5d35 in row_ins_sec_index_entry_low (flags=0, mode=2, index=0x7366e10, offsets_heap=0x7389038, heap=0x7368538, entry=0x7367ca8, trx_id=0, thr=0x7362980, dup_chk_only=false) at /mysql/mysql-5.7.26/storage/innobase/row/row0ins.cc:3033 #2 0x0000000001ae6cea in row_ins_sec_index_entry (index=0x7366e10, entry=0x7367ca8, thr=0x7362980, dup_chk_only=false) at /mysql/mysql-5.7.26/storage/innobase/row/row0ins.cc:3382 #3 0x0000000001ae6ea6 in row_ins_index_entry (index=0x7366e10, entry=0x7367ca8, thr=0x7362980) at /mysql/mysql-5.7.26/storage/innobase/row/row0ins.cc:3431 #4 0x0000000001ae73e2 in row_ins_index_entry_step (node=0x7362710, thr=0x7362980) at /mysql/mysql-5.7.26/storage/innobase/row/row0ins.cc:3579 #5 0x0000000001ae7749 in row_ins (node=0x7362710, thr=0x7362980) at /mysql/mysql-5.7.26/storage/innobase/row/row0ins.cc:3717 #6 0x0000000001ae7bae in row_ins_step (thr=0x7362980) at /mysql/mysql-5.7.26/storage/innobase/row/row0ins.cc:3853 #7 0x0000000001b05f73 in row_insert_for_mysql_using_ins_graph (mysql_rec=0x736e7a0 "\375\003", prebuilt=0x7362170) at /mysql/mysql-5.7.26/storage/innobase/row/row0mysql.cc:1738 #8 0x0000000001b06484 in row_insert_for_mysql (mysql_rec=0x736e7a0 "\375\003", prebuilt=0x7362170) at /mysql/mysql-5.7.26/storage/innobase/row/row0mysql.cc:1859 #9 0x00000000019adca9 in ha_innobase::write_row (this=0x736e4b0, record=0x736e7a0 "\375\003") at /mysql/mysql-5.7.26/storage/innobase/handler/ha_innodb.cc:7598 #10 0x0000000000f67dc0 in handler::ha_write_row (this=0x736e4b0, buf=0x736e7a0 "\375\003") at /mysql/mysql-5.7.26/sql/handler.cc:8062 #11 0x00000000017cf173 in write_record (thd=0x6dd5660, table=0x735afa0, info=0x7fffec435b50, update=0x7fffec435ad0) at /mysql/mysql-5.7.26/sql/sql_insert.cc:1873 #12 0x00000000017cc24a in Sql_cmd_insert::mysql_insert (this=0x6debbc8, thd=0x6dd5660, table_list=0x6deb638) at /mysql/mysql-5.7.26/sql/sql_insert.cc:769 #13 0x00000000017d2d7f in Sql_cmd_insert::execute (this=0x6debbc8, thd=0x6dd5660) at /mysql/mysql-5.7.26/sql/sql_insert.cc:3118 #14 0x000000000159a70c in mysql_execute_command (thd=0x6dd5660, first_level=true) at /mysql/mysql-5.7.26/sql/sql_parse.cc:3596 #15 0x00000000015a06c0 in mysql_parse (thd=0x6dd5660, parser_state=0x7fffec437610) at /mysql/mysql-5.7.26/sql/sql_parse.cc:5570 #16 0x0000000001595283 in dispatch_command (thd=0x6dd5660, com_data=0x7fffec437d80, command=COM_QUERY) at /mysql/mysql-5.7.26/sql/sql_parse.cc:1484 #17 0x00000000015940bc in do_command (thd=0x6dd5660) at /mysql/mysql-5.7.26/sql/sql_parse.cc:1025 #18 0x00000000016cbf91 in handle_connection (arg=0x6dda360) at /mysql/mysql-5.7.26/sql/conn_handler/connection_handler_per_thread.cc:306 #19 0x0000000001921c64 in pfs_spawn_thread (arg=0x6d7d0f0) at /mysql/mysql-5.7.26/storage/perfschema/pfs.cc:2190 #20 0x0000003823e079d1 in start_thread () from /lib64/libpthread.so.0 #21 0x0000003823ae8b6d in clone () from /lib64/libc.so.6
如果存在重復的行,這需要進(jìn)行判斷了:
如果重復的行正在被其他事務(wù)持有,那么需要進(jìn)行進(jìn)行隱試鎖轉換,主鍵的轉換操作如下:
->lock_clust_rec_read_check_and_lock ->lock_rec_convert_impl_to_expl ->lock_rec_convert_impl_to_expl_for_trx
因為我們知道通常insert鎖并不會(huì )建立顯示的鎖。對于如果出現了重復的行,持有重復行數據的事務(wù)并沒(méi)有提交或者回滾,需要其事務(wù)完成提交或者回滾,然后再進(jìn)行相應的拋錯或者繼續插入。需要注意的是對于replace/insert on dup 在進(jìn)行唯一性檢查的時(shí)候,通常加的LOCK_S鎖,而其他操作通常加的是 LOCK_X。
如果重復的行沒(méi)有其他事務(wù)持有,那么拋出重復行錯誤,但是注意這里的錯誤不是返回給客戶(hù)端的錯誤,是內部錯誤HA_ERR_FOUND_DUPP_KEY,這個(gè)錯誤在Innodb層叫做DB_DUPLICATE_KEY(convert_error_code_to_mysql)。如何處理這個(gè)錯誤就和相應的語(yǔ)法有關(guān)了。
當然如果沒(méi)有重復的行,那么接下來(lái)就可以繼續進(jìn)行insert插入操作了,Insert ignore/replace實(shí)現都是進(jìn)行insert操作。如果有重復行呢?那么接下來(lái)進(jìn)行分析。
這里我們也很明白了,對于了insert ignore/replace是通過(guò)主鍵/唯一鍵進(jìn)行判斷是否重復行的,具體點(diǎn)來(lái)說(shuō)就是如何處理錯誤HA_ERR_FOUND_DUPP_KEY。
如果表中一個(gè)能夠判斷唯一性的索引都沒(méi)有,那么即便2條數據一模一樣也不會(huì )標記為重復行,視為2條不同的數據,當然insert on dup 這里也是同樣的邏輯。
在進(jìn)行唯一性檢測的時(shí)候,會(huì )先檢查主鍵的唯一性,然后依次檢查各個(gè)唯一索引的唯一性是否滿(mǎn)足。
首先對于多行插入和insert select來(lái)講,每次innodb層插入的行數為1行,我們應該牢牢樹(shù)立以行為單位的處理流程,我們可以在函數Sql_cmd_insert::mysql_insert 中找到 一個(gè)大的while 循環(huán),這就是處理的循環(huán)。
我們也需要明白,進(jìn)行判斷唯一性的時(shí)候是先判斷主鍵的唯一性,如果滿(mǎn)足則插入主鍵數據,然后依次判斷二級唯一索引,如果滿(mǎn)足則進(jìn)行插入。這里涉及到一個(gè)問(wèn)題,如果主鍵數據插入了,但是二級唯一索引由于違法唯一性那么,前面主鍵插入的數據是需要回滾的。再或者我們執行的insert select操作,其中前面的一些行不違反唯一性插入了,但是隨后的某行違法了唯一性,那么前面插入的數據也是需要回滾的。函數row_insert_for_mysql_using_ins_graph 中進(jìn)行這種邏輯處理。
回滾棧幀:
#0 row_undo_ins (node=0x73685c0, thr=0x73671e8) at /mysql/mysql-5.7.26/storage/innobase/row/row0uins.cc:466 #1 0x0000000001b5d918 in row_undo (node=0x73685c0, thr=0x73671e8) at /mysql/mysql-5.7.26/storage/innobase/row/row0undo.cc:327 #2 0x0000000001b5dbae in row_undo_step (thr=0x73671e8) at /mysql/mysql-5.7.26/storage/innobase/row/row0undo.cc:411 #3 0x0000000001ab1847 in que_thr_step (thr=0x73671e8) at /mysql/mysql-5.7.26/storage/innobase/que/que0que.cc:1047 #4 0x0000000001ab1a63 in que_run_threads_low (thr=0x73671e8) at /mysql/mysql-5.7.26/storage/innobase/que/que0que.cc:1111 #5 0x0000000001ab1c25 in que_run_threads (thr=0x73671e8) at /mysql/mysql-5.7.26/storage/innobase/que/que0que.cc:1151 #6 0x0000000001bc622b in trx_rollback_to_savepoint_low (trx=0x7fffedc5b8c0, savept=0x7fffec434b60) at /mysql/mysql-5.7.26/storage/innobase/trx/trx0roll.cc:118 #7 0x0000000001bc64d2 in trx_rollback_to_savepoint (trx=0x7fffedc5b8c0, savept=0x7fffec434b60) at /mysql/mysql-5.7.26/storage/innobase/trx/trx0roll.cc:159 #8 0x0000000001b03b92 in row_mysql_handle_errors (new_err=0x7fffec434b5c, trx=0x7fffedc5b8c0, thr=0x7362980, savept=0x7fffec434b60) at /mysql/mysql-5.7.26/storage/innobase/row/row0mysql.cc:767 #9 0x0000000001b05ff2 in row_insert_for_mysql_using_ins_graph (mysql_rec=0x736e7a0 "\375\n", prebuilt=0x7362170) at /mysql/mysql-5.7.26/storage/innobase/row/row0mysql.cc:1752 #10 0x0000000001b06484 in row_insert_for_mysql (mysql_rec=0x736e7a0 "\375\n", prebuilt=0x7362170) at /mysql/mysql-5.7.26/storage/innobase/row/row0mysql.cc:1859 #11 0x00000000019adca9 in ha_innobase::write_row (this=0x736e4b0, record=0x736e7a0 "\375\n") at /mysql/mysql-5.7.26/storage/innobase/handler/ha_innodb.cc:7598 #12 0x0000000000f67dc0 in handler::ha_write_row (this=0x736e4b0, buf=0x736e7a0 "\375\n") at /mysql/mysql-5.7.26/sql/handler.cc:8062 #13 0x00000000017cf173 in write_record (thd=0x6dd5660, table=0x735afa0, info=0x7fffec435b50, update=0x7fffec435ad0) at /mysql/mysql-5.7.26/sql/sql_insert.cc:1873 #14 0x00000000017cc24a in Sql_cmd_insert::mysql_insert (this=0x6debbc8, thd=0x6dd5660, table_list=0x6deb638) at /mysql/mysql-5.7.26/sql/sql_insert.cc:769 #15 0x00000000017d2d7f in Sql_cmd_insert::execute (this=0x6debbc8, thd=0x6dd5660) at /mysql/mysql-5.7.26/sql/sql_insert.cc:3118 #16 0x000000000159a70c in mysql_execute_command (thd=0x6dd5660, first_level=true) at /mysql/mysql-5.7.26/sql/sql_parse.cc:3596 #17 0x00000000015a06c0 in mysql_parse (thd=0x6dd5660, parser_state=0x7fffec437610) at /mysql/mysql-5.7.26/sql/sql_parse.cc:5570 #18 0x0000000001595283 in dispatch_command (thd=0x6dd5660, com_data=0x7fffec437d80, command=COM_QUERY) at /mysql/mysql-5.7.26/sql/sql_parse.cc:1484 #19 0x00000000015940bc in do_command (thd=0x6dd5660) at /mysql/mysql-5.7.26/sql/sql_parse.cc:1025 #20 0x00000000016cbf91 in handle_connection (arg=0x6dda360) at /mysql/mysql-5.7.26/sql/conn_handler/connection_handler_per_thread.cc:306 #21 0x0000000001921c64 in pfs_spawn_thread (arg=0x6d7d0f0) at /mysql/mysql-5.7.26/storage/perfschema/pfs.cc:2190 #22 0x0000003823e079d1 in start_thread () from /lib64/libpthread.so.0 #23 0x0000003823ae8b6d in clone () from /lib64/libc.so.6
如果有重復的行并且產(chǎn)生了錯誤HA_ERR_FOUND_DUPP_KEY ,那么就不能進(jìn)行insert 操作了,這里就會(huì )根據不同的語(yǔ)法進(jìn)行不同的操作了。我們在函數(write_record )中可以找到這種分支處理邏輯。
實(shí)際上在處理重復行錯誤的時(shí)候,在內部分為了3種方式如下:
enum enum_duplicates { DUP_ERROR, DUP_REPLACE, DUP_UPDATE };
DUP_ERROR:這個(gè)代表的就是普通的insert/insert ignore語(yǔ)句
這是我們普通的操作,如果是insert操作則進(jìn)行拋錯給客戶(hù)端,如果是insert ignore操作則不進(jìn)行報錯,僅僅做一個(gè)警告,如下:
```
/
If IGNORE option is used, handler errors will be downgraded
to warnings and don’t have to stop the iteration.
/
mysql> insert ignore into tpk2 values(5,’g’,’m’);
Query OK, 0 rows affected, 1 warning (6 min 3.60 sec)
mysql> show warnings
-> ;
+————-+———+———————————————————-+
| Level | Code | Message |
+————-+———+———————————————————-+
| Warning | 1062 | Duplicate entry ‘5’ for key ‘PRIMARY’ |
+————-+———+———————————————————-+
1 row in set (0.00 sec)
但是需要注意的是,當前版本報錯后,自增值并不會(huì )回退。 - DUP_REPLACE:這里代表的是replace操作,也就是使用新的插入數據完全代替違反唯一性約束的行數據。我們通常理解的replace是delete/insert的結合,但是實(shí)際上并不完全是這樣,拋開(kāi)外鍵觸發(fā)器等因素,如果違反的唯一性是最后一個(gè)檢查的唯一索引的時(shí)候,還是會(huì )使用update代替,其他情況下就是delete/insert的結合了。如下我們可以看看:
if (last_uniq_key(table,key_nr) &&//是否是檢測的最后一個(gè)唯一索引
!table->file->referenced_by_foreign_key() &&
(!table->triggers || !table->triggers->has_delete_triggers()))
{
if ((error=table->file->ha_update_row(table->record[1],
table->record[0])) && //調用了是update接口
error != HA_ERR_RECORD_IS_THE_SAME)
if (error != HA_ERR_RECORD_IS_THE_SAME)
info->stats.deleted++; //影響行數+1
goto after_trg_n_copied_inc;
}
else
{
…
if ((error=table->file->ha_delete_row(table->record[1])))//刪除接口 delete
goto err;
info->stats.deleted++; //影響行數+1
…
/ Let us attempt do write_row() once more ///這里會(huì )進(jìn)行一次循環(huán)進(jìn)行普通的insert操作
}
- DUP_UPDATE:這個(gè)代表我們的語(yǔ)法insert into on dup,這里就完全等待于update語(yǔ)句了。需要注意的是,這里不是完全替代,會(huì )使用語(yǔ)句中的update進(jìn)行字段的更新,其他字段并不會(huì )更新,如下:
if ((error=table->file->ha_update_row(table->record[1], table->record[0])) && //調入update接口 error != HA_ERR_RECORD_IS_THE_SAME) {
…
if (error != HA_ERR_RECORD_IS_THE_SAME) info->stats.updated++;//影響行數+1 else error= 0;
再說(shuō)對于普通的insert操作而言,影響的行數通常為1。replace/insert into on dup如果遇到了**重復行更改后**(注意不是直接插入成功的狀態(tài)),通常返回影響的行數為2如下:
mysql> replace testpri2(id,a,b) values(7,’b’,’k’);
Query OK, 2 rows affected (2.74 sec)
mysql> insert into testpri2(a,b) values(‘mmmmnb’,’ffhhh’) on DUPLICATE KEY UPDATE b=’bj’;
Query OK, 2 rows affected (3.81 sec)
- replace:如果是調用update接口則,不會(huì )真正執行insert操作,會(huì )在info->stats.deleted++進(jìn)行影響行數+1,最后在insert接口中還會(huì )+1。如果是調用delete/insert操作會(huì )真正執行insert操作,正常的info->stats.deleted++進(jìn)行+1操作,最后在insert接口中還會(huì )+1。因此它們結果還是2。 - insert into on dup:肯定調用update接口,不會(huì )真正執行insert操作,會(huì )在 info->stats.updated++;進(jìn)行影響行數+1,然后會(huì )直接進(jìn)行info->stats.copied++進(jìn)行影響行數+1,因此為2。 因此不管怎么看起來(lái)都是影響行數為2,也不要奇怪。 ###其他: DML回執接口:
updated_return=0x7fffec435d20) at /mysql/mysql-5.7.26/sql/sql_update.cc:1092
at /mysql/mysql-5.7.26/sql/sql_update.cc:2891
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自本網(wǎng)站內容采集于網(wǎng)絡(luò )互聯(lián)網(wǎng)轉載等其它媒體和分享為主,內容觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如侵犯了原作者的版權,請告知一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容,聯(lián)系我們QQ:712375056,同時(shí)歡迎投稿傳遞力量。
Copyright ? 2009-2022 56dr.com. All Rights Reserved. 特網(wǎng)科技 特網(wǎng)云 版權所有 特網(wǎng)科技 粵ICP備16109289號
域名注冊服務(wù)機構:阿里云計算有限公司(萬(wàn)網(wǎng)) 域名服務(wù)機構:煙臺帝思普網(wǎng)絡(luò )科技有限公司(DNSPod) CDN服務(wù):阿里云計算有限公司 百度云 中國互聯(lián)網(wǎng)舉報中心 增值電信業(yè)務(wù)經(jīng)營(yíng)許可證B2
建議您使用Chrome、Firefox、Edge、IE10及以上版本和360等主流瀏覽器瀏覽本網(wǎng)站