zoukankan      html  css  js  c++  java
  • Bug #19528825 "UNABLE TO PURGE A RECORD"

    概述:

          在生产环境中,当开启insert buffer时(参数innodb_change_buffering=all),部分实例偶尔会出现“UNABLE TO PURGE A RECORD”错误。这个其实是一个老bug,从官方bug系统来看,也有其它用户遇到过类似的问题,并且官方在今年2月份已经对该bug进行了修复。

    insert buffer:

          在描述问题的产生原因和解决方案之前,首先来简单说说insert buffer。简单来说,insert buffer是二级索引操作的一个缓存。在进行二级索引操作时,若请求的page不在buffer pool中,则将二级索引操作进行缓存,待下次读取该page时,将page与buffer中的操作合并,通过这种方式,可以减少在更新(INSERT,DELETE,UPDATE等)操作时磁盘的随机读,提高更新的响应时间。

          最早insert buffer仅支持insert 操作的buffer,所以这种buffer叫insert buffer,到mysql5.6时,不仅支持insert操作的buffer操作,还可以支持update,delete,purge等操作的buffer功能,而insert buffer也改称changing buffer,后面我简称ibuf。通过参数innodb_change_buffering设置,用户可以灵活控制二级索引操作的buffer开启与否。ibuf仅支持普通二级索引,对于唯一索引和主键索引不起作用。我们知道,innodb表是索引组织表,每个表由一个聚簇索引和若干个二级索引组成。若使用ibuf,则对于每个二级索引的更新操作,都会对应产生一个ibuf操作符,下表列出了ibuf操作符与更新语句的对应关系:

    操作

    Insert buffer操作符

    说明

    Insert

    IBUF_OP_INSERT

     

    Delete

    IBUF_OP_DELETE_MARK

    Delete语句实际是对删除记录打标

    Purge

    IBUF_OP_DELETE

    Purge真正物理上清理打删除标的记录

    Update

    IBUF_OP_DELETE_MARK

    IBUF_OP_INSERT

    二级索引的update由Delete+Insert组成。

    purge:

          上节提到的purge其实并非用户发起的更新语句,而是innodb存储引擎实现MVCC(多版本并发控制)时,需要的一个后台清理操作。Innodb的多版本实现机制(利用回滚段保存历史版本信息,并通过记录中的ROLLPTR与回滚段记录进行关联)在删除或更新记录时,并非真正物理删除,而仅仅是在记录上打上删除标记,通过后台线程进行清理,这个过程就是purge过程。当然,purge还有另外一个作用是清理回滚段,回收空间,以便空间可以重复利用。有关purge和mvcc的实现,有机会在单独整理一篇文章。

    问题产生的原因:

        回到问题本身,我们通过一个简单的场景来复现问题。假设存在表t(id int, c1 varchar(100),primary key(id),key(c1)),表中包含一条记录(1,a);并且假设表t的page都不在buffer pool中,以便可以使用ibuf。通过下表的操作序列,可以复现问题。

    操作序列

    更新语句

    Ibuf操作

    1【DELETE】

    delete from t where id=1;

     

    IBUF_OP_DELETE_MARK

    2【INSERT】

    insert into t values(1,’a’);

     

    IBUF_OP_INSERT

    3【PURGE】

     

    IBUF_OP_DELETE

    在读取page的过程中,会进行合并操作,合并操作的顺序是以对应page在ibuf中的操作顺序来进行的,在执行到第3步:IBUF_OP_DELETE时,发现待删除的记录并没有打删除标记(第2步插入了相同主键+二级索引的记录,将删除标记清理),认为异常,导致抛错。由于purge操作是由单独的线程在后台执行,因此执行更新语句的操作与purge操作并没有严格的先后顺序,如果上述操作的顺序变为1->3->2则不会复现问题。

    重要流程:

         从上节分析来看,产生问题的主要原因是purge操作和更新操作没有严格的同步,导致purge可能清理到未打删除标记的记录。

    purge流程:

    1. 读取解析undo记录,TRX_UNDO_DEL_MARK_REC类型的回滚记录执行purge
    2. 调用函数row_purge_del_mark
    3. 若表含有二级索引,先purge二级索引(row_purge_remove_sec_if_poss)
    4. 判断purge二级索引是否可以缓存,确定是否执行删除物理记录动作
    5. purge主键索引(row_purge_remove_clust_if_poss)

    函数调用关系:

    srv_do_purge->trx_purge->que_thr_step->row_purge_step
    ->row_purge->row_purge_record_func->row_purge_del_mark

    ->row_purge_remove_sec_if_poss-> row_purge_remove_sec_if_poss_leaf                                     

    purge二级索引流程:

    1. 读取page,使用ibuf_should_try函数判断是否可以使用ibuf
    2. 根据返回结果确定读取page的方式,如果是purge,则方式设置为:BUF_GET_IF_IN_POOL_OR_WATCH
    3. 若page不在buffer-pool中,设置watch标记,表示有purge请求
    4. 调用ibuf_insert函数进行ibuf缓存操作,并设置标记为:BTR_CUR_DELETE_IBUF
    5. 根据返回结果,row_purge_del_mark确定是否真正执行删除动作
    6. 结束。

    判断是否使用ibuf调用关系:

    row_purge_remove_sec_if_poss_leaf->row_search_index_entry->btr_pcur_open_func->btr_cur_search_to_nth_level->ibuf_should_try

    更新操作与purge操作在ibuf中的协同:

          purge线程获取page时,若page不在buffer中,将page设置watch标记,然后执行ibuf_insert将purge操作缓存。更新操作(insert,delete,update等)也调用ibuf_insert操作进行buffer,首先会判断page是否有watch标记,若存在,则认为可能 与purge动作冲突,不能使用ibuf。此时,会去从磁盘读取page,在读取page过程中会将purge操作进行合并,后续进行更新操作则不会存在问题。通过watch标记来达到更新操作和purge操作协同使用ibuf的目的,避免上述提到的问题。

          到这里,大家可能会有一个疑问,假设不使用ibuf,正常的更新和purge操作同样是在不同的线程,也有可能出现(DELETE,INSERT,PURGE)序列,为啥就没有问题呢?因为在purge二级索引时,还会调用row_purge_poss_sec函数,确认记录是否可以purge(二级索引记录对应的聚集索引没有delete mark或者trx_id比purge view还新时,不能purge),从而避免上述问题。      

    解决方法:

           同样在purge二级索引过程中,btr_cur_search_to_nth_level,首先调用buf_page_get_gen函数进行watch设置,然后调用row_purge_poss_sec判断记录是否可以purge,若用户已经re-insert,则此时purge动作忽略;否则,表示还没有insert记录进来,继续执行调用ibuf_insert接口进行缓存操作。另一方面,更新操作,insert在使用ibuf时,会判断是否有watch标记,但程序逻辑在返回时,将标记丢了,导致出现问题。因此只要保证更新操作真正使用ibuf前,检查没有purge同时使用ibuf,则可以避免问题发生。

    详细解法可以参考:

    https://github.com/mysql/mysql-server/commit/ec369cb4f363161dfbbbd662b20763b54808b7d1

    参考文档:

    http://mysqllover.com/?p=1264

    https://bugs.mysql.com/bug.php?id=73767

    http://hedengcheng.com/?p=94

    http://mysql.taobao.org/monthly/2015/04/01/

  • 相关阅读:
    图片的像素和位图相关的知识
    LeetCode第四十四题-字符串匹配
    LeetCode第四十三题-字符串相乘
    LeetCode第四十二题-可装雨水的容量
    LeetCode第四十一题-寻找数组中的最小的缺失正整数
    LeetCode第四十题-求组合所有可能性(2)
    LeetCode第三十九题-求组合所有可能性
    LeetCode第三十八题-报数
    LeetCode第三十七题-实现数独
    LeetCode第三十六题-判断九宫格是否有效
  • 原文地址:https://www.cnblogs.com/cchust/p/4544518.html
Copyright © 2011-2022 走看看