代码改变世界

pt-osc在线修改表发生死锁问题分析及思考

2017-06-22 10:37  Kevin.hhl  阅读(3363)  评论(0)    收藏  举报

前提背景描述:

       MySQL 版本: MySQL community  5.6.16   

       pt-online-schema-change 2.2.20 

问题描述:使用pt-online-schema-change 修改字段varchar(N)  N变长。

 

死锁日志如下:

------------------------
 
LATEST DETECTED DEADLOCK
 
------------------------
 
2017-06-19 21:20:18 7f21283be700
 
*** (1) TRANSACTION:
 
TRANSACTION 14674152324, ACTIVE 0.162 sec setting auto-inc lock
 
mysql tables in use 2, locked 2
 
LOCK WAIT 6 lock struct(s), heap size 11844 row lock(s), undo log entries 1
 
LOCK BLOCKING MySQL thread id14850 block 27862
 
MySQL thread id 27862, OS thread handle 0x7f21b37ff700, query id 16149004 10.25.129.0 xx_rw update
 
REPLACE INTO `xx`.`_users_profile_new` (`id`, 省略若干字段) VALUES (NEW.`id`,省略若干字段
 
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
 
TABLE LOCK table `xx`.`_users_profile_new` trx id 14674152324 lock mode AUTO-INC waiting
 
*** (2) TRANSACTION:
 
TRANSACTION 14674152323, ACTIVE 0.162 sec fetching rows
 
mysql tables in use 2, locked 2
 
150 lock struct(s), heap size 138645114 row lock(s), undo log entries 2016
 
MySQL thread id 14850, OS thread handle 0x7f21283be700, query id 16148991 10.46.161.106 xx_rw Sending data
 
INSERT LOW_PRIORITY IGNORE INTO `xx`.`_users_profile_new` (`id`, `changed_time`,省略若干字段 ) SELECT `id`, `changed_time`,省略若干字段)
 
*** (2) HOLDS THE LOCK(S):
 
TABLE LOCK table `xx`.`_users_profile_new` trx id 14674152323 lock mode AUTO-INC
 
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
 
RECORD LOCKS space id 519 page no 637613 n bits 112 index `PRIMARY` of table `xx`.`users_profile` trx id 14674152323 lock mode S lo
 
cks rec but not gap waiting
 
Record lock, heap no 41 PHYSICAL RECORD: n_fields 37; compact format; info bits 0
 
 0len 4hex 80dd6fa7; asc   o ;;
 
 1len 6hex 00036aa5cb84; asc   j   ;;
 
 2len 7hex 13000100180e20; asc        ;;
 
 3len 8hex 999ce6d51205a4d2; asc         ;;
 
 4len 9hex e591a8e6b0b8e99fa9; asc          ;;
 
 5len 18hex 353131353233313939373038323831373338; asc 511523199708281738;;
 
 6len 6hex e59b9be5b79d; asc       ;;
 
 7len 6hex e5ae9ce5aebe; asc       ;;
 
 8len 30hex e59b9be5b79de79c81e6b19fe5ae89e58ebfe5ba95e893ace5ae89e99587; asc                               ;;
 
 9len 4hex 80000006; asc     ;;
 
 10len 21hex 32343831343234345f676972666c476c61417a5f61; asc 24814244_girflGlaAz_a;;
 
 11len 21hex 32343831343234345f37426e7a426c336837585f63; asc 24814244_7BnzBl3h7X_c;;
 
 12len 19hex 36323137393936373130303036393437393237; asc 6217996710006947927;;
 
 13len 24hex e4b8ade59bbde982aee694bfe582a8e89384e993b6e8a18c; asc                         ;;
 
 14len 30hex 5b7b2250686f6e65223a223138393930393331343038222c224e616d6522; asc [{"Phone":"18990931408","Name"; (total 185 bytes);
 
 15len 6hex e58cbbe7949f; asc       ;;
 
 16len 6hex e58cbbe999a2; asc       ;;
 
 17len 27hex e68890e983bde5b882e9be99e6b389e9a9bfe4b8ade58cbbe999a2; asc                            ;;
 
 18len 6hex e68890e983bd; asc       ;;
 
 19len 11hex 3133353431313536303631; asc 13541156061;;
 
 20len 23hex e998b3e58589e59f8ee5b9b8e7a68fe8b7af3237e58fb7; asc                   27   ;;
 
 21len 4hex 817aa2a4; asc  z  ;;
 
 22len 6hex e59b9be5b79d; asc       ;;
 
 23len 6hex e5ae9ce5aebe; asc       ;;
 
 24len 11hex 3133353431313536303631; asc 13541156061;;
 
 25len 6hex e59b9be5b79d; asc       ;;
 
 26len 4hex 80000000; asc     ;;
 
 27: SQL NULL;
 
 28len 4hex 80000000; asc     ;;
 
 29len 0hex ; asc ;;
 
 30len 0hex ; asc ;;
 
 31len 0hex ; asc ;;
 
 32len 0hex ; asc ;;
 
 33len 4hex 80000000; asc     ;;
 
 34len 9hex e9be99e6b389e9a9bf; asc          ;;
 
 35len 21hex 32343831343234345f69714d793635413578545f62; asc 24814244_iqMy65A5xT_b;;
 
 36len 6hex e6b19fe5ae89; asc       ;;
 
*** WE ROLL BACK TRANSACTION (1)

就此对上面的死锁日志分析2个并发事务如下:

事务1:

   持有原表:users_profile 某行或多行的X锁, 等待新表:_users_profile_new 的auto_inc 锁。

事务2:

   持有新表:_users_profile_new 的auto_inc 锁, 等待原表:users_profile 多行的S锁。

从上面看出两个事务执行到此满足死锁条件,MySQL最终回滚事务1。

下面描述2个并发事务:

事务1:是pt-osc 建立的触发器,原表的更新后触发触发器把数新的更新替换到新表(在同一个事务里面),可以表示如下:

           begin;  

                 update users_profile set ... ;

                 REPLACE INTO `xx`.`_users_profile_new`     

                 死锁发生 

           rollback;

事务2:是pt-osc 批量从原表查询(select * from 原表 where id>=x1  and id<x2 lock in shared mode )后插入数据到新表,可以表示如下:

           insert into _new select * from _old where ... lock in share mode ;

 

思考问题:

1.使用pt-osc 如何减少死锁的发生?

   根据pt-osc 原理,不能完全避免死锁的产生,但是可以减少,可以在业务低峰使用pt-osc,chunk_size 设置的更小。

2.发生死锁会不会导致数据不一致?

   不会造成数据不一致。 如果发生死锁会导致原表的更新会丢失,就是事务被回滚了,对应用不友好。

3.会不会导致主从数据不一致?

   binlog_format=ROW          不会导致主从数据不一致。

   binlog_format=statement   因触发器在slave 也会被触发,如果主上没有死锁,从库上发生了死锁,有可能导致主从数据不一致。

 

证明原sql和触发器触发的sql在同一个事务里:

结论:从binlog 看触发器触发的sql 和原sql在同一事务里面。

# at 131663
 
#170621 18:08:07 server id 249623306  end_log_pos 131796 CRC32 0xf7f6676e       Write_rows: table id 119 flags: STMT_END_F
 
### UPDATE `xx`.`users_profile`
 
### WHERE
 
###   @1=2 /* INT meta=0 nullable=0 is_null=0 */
 
### SET
 
###   @10='urlagrr' /* VARSTRING(112) meta=112 nullable=0 is_null=0 */
 
### DELETE FROM `xx`.`_users_profile_new`
 
### WHERE
 
###   @1=2 /* INT meta=0 nullable=0 is_null=0 */
 
### INSERT INTO `xx`.`_users_profile_new`
 
### SET
 
###   @1=2 /* INT meta=0 nullable=0 is_null=0 */
 
###   @2='2017-04-24 11:07:08.000000' /* DATETIME(6) meta=6 nullable=0 is_null=0 */
 
###   @3='' /* VARSTRING(80) meta=80 nullable=0 is_null=0 */
 
###   @4=NULL /* VARSTRING(80) meta=72 nullable=1 is_null=1 */
 
###   @5='' /* VARSTRING(80) meta=80 nullable=0 is_null=0 */
 
###   @6='' /* VARSTRING(80) meta=80 nullable=0 is_null=0 */
 
###   @7='' /* VARSTRING(400) meta=400 nullable=0 is_null=0 */
 
###   @8=0 /* INT meta=0 nullable=0 is_null=0 */
 
###   @9='' /* VARSTRING(112) meta=112 nullable=0 is_null=0 */
 
###   @10='urlagrr' /* VARSTRING(112) meta=112 nullable=0 is_null=0 */
 
###   @11='' /* VARSTRING(128) meta=128 nullable=0 is_null=0 */
 
###   @12='' /* VARSTRING(80) meta=80 nullable=0 is_null=0 */
 
###   @13='' /* VARSTRING(800) meta=800 nullable=0 is_null=0 */
 
###   @14='' /* VARSTRING(80) meta=80 nullable=0 is_null=0 */
 
###   @15='' /* VARSTRING(80) meta=80 nullable=0 is_null=0 */
 
###   @16='' /* VARSTRING(160) meta=160 nullable=0 is_null=0 */
 
###   @17='' /* VARSTRING(80) meta=80 nullable=0 is_null=0 */
 
###   @18='' /* VARSTRING(80) meta=80 nullable=0 is_null=0 */
 
###   @19='' /* VARSTRING(400) meta=400 nullable=0 is_null=0 */
 
###   @20=2 /* INT meta=0 nullable=0 is_null=0 */
 
###   @21='' /* VARSTRING(80) meta=80 nullable=0 is_null=0 */
 
###   @22='' /* VARSTRING(80) meta=80 nullable=0 is_null=0 */
 
###   @23='special_xx' /* VARSTRING(80) meta=80 nullable=0 is_null=0 */
 
###   @24='' /* VARSTRING(80) meta=80 nullable=0 is_null=0 */
 
###   @25=2 /* INT meta=0 nullable=0 is_null=0 */
 
###   @26='2017-04-24 11:07:08.000000' /* DATETIME(6) meta=6 nullable=1 is_null=0 */
 
###   @27=10 /* INT meta=0 nullable=0 is_null=0 */
 
###   @28='' /* VARSTRING(128) meta=128 nullable=0 is_null=0 */
 
###   @29='' /* VARSTRING(64) meta=64 nullable=0 is_null=0 */
 
###   @30='' /* VARSTRING(80) meta=80 nullable=0 is_null=0 */
 
###   @31='' /* VARSTRING(80) meta=80 nullable=0 is_null=0 */
 
###   @32=3 /* INT meta=0 nullable=0 is_null=0 */
 
###   @33='' /* VARSTRING(80) meta=80 nullable=0 is_null=0 */
 
###   @34='' /* VARSTRING(112) meta=112 nullable=0 is_null=0 */
 
###   @35='' /* VARSTRING(80) meta=80 nullable=0 is_null=0 */
 
# at 131796
 
#170621 18:08:12 server id 249623306  end_log_pos 131827 CRC32 0xd6ed36bc       Xid = 4041
 
COMMIT/*!*/;