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/

  • 相关阅读:
    Codeforces 1045C Hyperspace Highways (看题解) 圆方树
    Codeforces 316E3 线段树 + 斐波那切数列 (看题解)
    Codeforces 803G Periodic RMQ Problem 线段树
    Codeforces 420D Cup Trick 平衡树
    Codeforces 295E Yaroslav and Points 线段树
    Codeforces 196E Opening Portals MST (看题解)
    Codeforces 653F Paper task SA
    Codeforces 542A Place Your Ad Here
    python基础 异常与返回
    mongodb 删除
  • 原文地址:https://www.cnblogs.com/cchust/p/4544518.html
Copyright © 2011-2022 走看看