insert 插入耗时异常问题

Posted 渔夫数据库笔记

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了insert 插入耗时异常问题相关的知识,希望对你有一定的参考价值。

1.数据库版本

8.0.13(阿里云polardb)

2.问题发现

监控发现某时刻数据库活跃连接突然上升,查询发现当时有一张表上有大量并发的倒排序查询,及并发insert操作(每个insert语句只是插入一条记录),截图如下:

 具体现象是某个倒排序查询批量出现,并且查询时间很长时(正常情况下该倒排序查询效率很高,很可能是因为order by limit选择执行计划导致某些时候执行效率很低)。会同时出现这张表上的insert操作耗时异常(插入一条记录可能耗时几秒到几十秒)。倒排序插入语句类似如下:

SELECT   
    id,
    merchant_id,
    product_type,
    order_no,
    user_id,
    stage,
    match_coupon_type,
    general_coupon_info,
    dynamic_coupon_info,
    request_param,
    create_time,
    create_by,
    update_time,
    egg_coupon_info,
    supply_app_id,
    car_level,
    original_fee_info,
    discounted_fee_info,
    gd_adjust_coupon_info,
    coupon_detail_array,
    estimate_id,
    order_count  
    FROM test_table
    WHERE
    order_no = 'xxxxxxx'   
    and (user_id = 'xxxxxxx' or user_id = '')
    and stage = 21 order by id desc limit 1

3.问题原因分析

3.1 上面现象产生的原因

阿里云给出的解释是因为,倒排序查询从后往前访问叶子节点page的时候,有可能需要获取root节点和非叶子节点的s latch,这样对于需要悲观插入的insert(先尝试乐观插入,乐观插入失败进行悲观插入),悲观插入因为需要给index 加上SX LOCK,并且给所有可能SMO 的相关也都加上X latch。这时两种可能就会发生相互影响及阻塞,并且后面的insert乐观插入失败,进行悲观插入的时候也会被阻塞在给 index 加 SX LOCK。

3.2 什么是 index lock ,RW latch

下面先简单介绍下 index lock 和 RW latch,这两个锁结构都是用来控制线程安全的并发访问B-tree的。index lock 是用来锁定整个index的(select需要对index 加 S lock,如果要修改索引结果则加SX lock)。RW latch 是对访问到的具体页面加的锁(5.7 开始 访问到的根节点,非叶子节点,叶子节点上都RW latch)。

3.3 查询和变更过程中index lock 及 RW latch 的加锁操作

  一个select查询的加锁大致如下,先给index 加上 S lock,然后沿着Btree搜索路径,给遇到的非叶子节点都加上S latch,直到找到要访问的第一个叶子节点加S latch,然后释放 上面给index,root,及非叶子节点加的锁。

 一个变更查询加锁导致如下,先给index 加上 S lock,然后沿着Btree搜索路径,给遇到的非叶子节点都加上S latch,直到找到要访问的第一个叶子节点加X latch,然后释放 上面给index,root,及非叶子节点加的锁。然后判断修改操作是否会导致Btree结构变化(如索引分裂)

1)如果不会,因为这时已经持有page 的X latch,直接修改,然后返回。

2)如果会,那么需要执行悲观插入操作,需要重新遍历Btree,这时给index 加SX lock,这时因为已经给index 加上了SX lock(SX lock 和 SX lock互斥)。那么搜索路径上的btree 的page 都不需要加 lock, 但是需要把搜索过程中的page 保存下来, 最后阶段给搜索路径上有可能发生结构变化的page 加x lock..这样就保证了在搜索的过程中, 对于read 操作的影响降到最低。

3.4 倒排序查询 index 和 RW latch 加锁有什么特别?为什么会产生这么大的影响

  如果是正向访问,在访问到第一个叶子节点后,就会时候index lock 及非叶子节点的 RW latch。然后通过叶子节点间的双向链表访问下一个节点,在持有当前叶子节点 RW latch 的保护下,对下一个叶子节点加RW latch。
  但是如果倒排序访问,也使用这种方案的话,就有可能出现死锁(加锁顺序不一致,一个从前往后,一个从后往前)。那么倒排序访问怎么加锁?
到排序访问在index lock 及 非叶子节点的 RW latch 保护下获取第一个访问到的叶子节点的 RW latch 后,也是通过双向链表向前访问前一个页,但是为了避免死锁,它在对前一个叶子节点加RW latch 时会先释放当前节点 RW latch。
这样他对前一个节点加的 RW latch 是在没有保护的情况下加的,需要通过 modify_clock(保存在内存中的一个结构,每个页面对应一个) 判断索引结构是否变更过,如果没有,则正常访问,如果变更过则需要重新走root节点进行加锁访问(走悲观流程,相对复杂,需要重新走一遍遍历流程,给index 加上 S lock,非叶子节点加上 S latch,并且每层可能要对两个page 加latch,确保中间不会分裂)

以上是关于insert 插入耗时异常问题的主要内容,如果未能解决你的问题,请参考以下文章

MySQL技术专题「性能优化系列」一直都倾向于优化查询,这次学习一下优化Insert插入语句

当自动关闭资源时尝试使用资源引发异常以及异常时会发生啥

插入的 NSManagedObject 无法保存时会发生啥情况?

在多线程 C++11 程序中未处理异常时会发生啥?

在多线程 C++11 程序中未处理异常时会发生啥?

当 .NET 线程抛出异常时会发生啥?