奇怪从库MTS并发回放比单SQL线程效率更低?

image.png

最近遇到一个问题,从库基于writeset并发的情况下,发现从库的worker线程CPU非常的低,导致大量的延迟,如果改为单SQL线程并发效率反而更高,CPU利用率也起来了,延迟消失,现分析如下。
对于主从并发,特别是基于writeset的并发,在主库生成last commit的时候并没有考虑到一些innodb特殊锁的存在(或者innodb的锁BUG)。因此在进行从库并发回放的时候就会出现锁堵塞的情况。遇到锁堵塞的时候就需要考虑到多线程之间唤醒的方式,这样worker线程才能继续下去,下面是多线程并发的时候唤醒的方式。

  • 本worker线程遇到行锁堵塞,直接反馈,并且解锁MDL LOCK(Commit_order_manager::check_and_report_deadlock-->Commit_order_manager::report_deadlock),这一这里MDL LOCK为每个woerker线程都有一把锁,需要精准唤醒,也就是唤醒正在处于Waiting for preceding transaction to commit状态的且堵塞了本worker线程进行数据更改的worker线程,然后加入锁等待队列。
lock_rec_lock_slow
  ->RecLock::add_to_waitq
    ->thd_report_row_lock_wait
      ->Commit_order_manager::check_and_report_deadlock
        ->Commit_order_manager::report_deadlock
          ->Slave_worker::report_commit_order_deadlock
  • 处于Waiting for preceding transaction to commit状态的worker唤醒后会重新判定自己是否堵塞了其他worker线程,(Commit_order_manager::wait_on_graph) ,唤醒可能是,
    A:正常的提交序列来到继续,唤醒状态为GRANTS
    B:被正在遭遇行堵塞的worker唤醒(上面的MDL LOCK解锁),唤醒后重新检测,唤醒状态为VICTIM
  • 如果是VICTIM状态唤醒的worker,那么不会进行提交,而是进行事务rollback,回滚后唤醒堵塞等待的worker线程(lock_reset_wait_and_release_thread_if_suspended ->que_thr_move_to_run_state),因为本worker线程可能行锁等待中,而其他worker的事务完成后进行retry transaction
  • retry transaction前自己有一个sleep过程。(slave_worker_exec_job_group)

整套唤醒过程主要是借助了MDL LOCK和row lock的唤醒机制交替在使用,处于Waiting for preceding transaction to commit状态的worker如果由于行锁堵塞了其他正在执行的线程,则被堵塞的线程会通过MDL LOCK唤醒处于处于Waiting for preceding transaction to commit状态的worker,其醒来后会回滚事务,然后通过行锁的机制反过来唤醒处于行锁等待的执行woker线程。假设worker2等待worker1事务提交后才能提交,worker1的事务由于worker2的行锁堵塞不能继续那么接下来发生的如下,

                WORKER1                WORKER2
                   --------------------
                 |                      |
                 |                      |
                 |                      |
                 | ----------------->   |事务执行加row lock
              事务执行加rowlock         |
              拿不到行锁                |
                 |                    准备提交事务等待提交序列
                 |                    Waiting for preceding 
                 |                    transaction to commit    
                 |                       
                 |------------------>   |                      
                 |  通过MDL LOCK唤醒    |
                 |                      |
                 |                  唤醒后回滚事务
                 |                      |
                 | <-----------------   |
                 |   通过row lock解锁   |
                事务继续                |
                 |                     事务retry
                 |                      |
               事务完成                 |
                 |                      |
                 |                     事务完成 

如果存在大量锁堵塞的情况下会导致MTS效率大大降低,效率远低于单线程,这套流程主要是如下时间耗用

  • 锁等待唤醒
  • 事务回滚
  • retry 事务和之前的sleep

因此如果有大量的锁冲突这套机制就会浪费大量的时间在等待上,并且CPU的利率非常低,既然CPU的利用率低,那么CPU自然就没有执行机器码,不执行机器码当然就跑不动(高级语言-->汇编语言-->机器码)。
而对于模拟来讲可以使用下面的方式(注意:8026以下版本才能模拟,模拟用的大量的replace语句),

mysql> show create table test.testpri2 \G
*************************** 1. row ***************************
       Table: testpri2
Create Table: CREATE TABLE `testpri2` (
  `id` int NOT NULL AUTO_INCREMENT,
  `a` int DEFAULT NULL,
  `b` int DEFAULT NULL,
  `c` int DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `a` (`a`,`b`)
) ENGINE=InnoDB AUTO_INCREMENT=43255 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

init data:
for ((i=0;i<10000;i++))  do  /newdata/mysql/mysql8023/install/bin/mysql -S'/newdata/mysql/mysql8023/tmp/mysql3329.sock'  -e "insert into test.testpri2(a,b,c) values($i,$i,$i)";do

Terminal 1:
for ((i=10000;i<20000;i++))  do  /newdata/mysql/mysql8023/install/bin/mysql -S'/newdata/mysql/mysql8023/tmp/mysql3329.sock'  -e "replace into test.testpri2(a,b,c) values($i,$i,$i)";done

Terminal 2:
for ((i=0;i<10000;i++))  do  /newdata/mysql/mysql8023/install/bin/mysql -S'/newdata/mysql/mysql8023/tmp/mysql3329.sock'  -e "replace into test.testpri2(a,b,c) values($i,$i,$i)";done
Terminal 3:
for ((i=0;i<10000;i++))  do  /newdata/mysql/mysql8023/install/bin/mysql -S'/newdata/mysql/mysql8023/tmp/mysql3329.sock'  -e "replace into test.testpri2(a,b,c) values($i,$i,$i)";done
Terminal 4:
for ((i=0;i<10000;i++))  do  /newdata/mysql/mysql8023/install/bin/mysql -S'/newdata/mysql/mysql8023/tmp/mysql3329.sock'  -e "replace into test.testpri2(a,b,c) values($i,$i,$i)";done

而对于8026版本或者以上,由于innodb修复了BUG,


image.png

因此无法模拟出来。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352

推荐阅读更多精彩内容