概述:

在生产环境中,当开启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/

Bug #19528825 "UNABLE TO PURGE A RECORD"的更多相关文章

  1. UNABLE TO PURGE A RECORD(二)

    上一篇文章说明了bug出现的原因和原理分析,要修复bug似乎已经水到渠成了,但远没有这么简单,只因为“并发”.要修复问题,首先要做的第一件事情是稳定的复现问题.由于数据库系统是一个并发系统,并且这个b ...

  2. ASP.Net Core 2.2 InProcess托管的Bug:unable to open database file

    最近把项目更新到了ASP.Net Core 2.2,发布之后发现在IIS下使用SQLite数据库不行了,报异常说不能打开数据库."unable to open database file&q ...

  3. [java] bug经验 Unable to locate Spring NamespaceHandler for XML schema namespace解决办法

    报错关键字: org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration probl ...

  4. 【bug】Unable to execute dex: Multiple dex files define

    This is a build path issue. Make sure your bin folder is not included in your build path. Right clic ...

  5. mysql的TABLE_SCHEMA的sql和information_schema表, MySQL管理一些基础SQL语句, Changes in MySQL 5.7.2

    3.查看库表的最后mysql修改时间, 如果第一次新建的表可能还没有update_time,所以这里用了ifnull,当update_time为null时用create_time替代 select T ...

  6. FNDCPASS Troubleshooting Guide For Login and Changing Applications Passwords

    In this Document   Goal   Solution   1. Error Starting Application Services After Changing APPS Pass ...

  7. [daily][device][bluetooth] 蓝牙怎么办!(archlinux下驱动蓝牙鼠标,以及三星手机)

    去年地摊买的破无线鼠标坏掉了.看上微软的Designer Mouse蓝牙鼠,但是买之前我要确认我能不能驱起来. 这款鼠标只支持蓝牙4.0.系统支持windows8以上,不支持xp和windows7. ...

  8. Excessive AWR Growth From Partitioned Objects Such as SYS.WRH$_EVENT_HISTOGRAM Causing Sysaux to Grow

    AWR数据增长较快,导致sysaux表空间使用较高 SQL> select f.tablespace_name, 2 a.total, 3 f.free, 4 round((f.free / a ...

  9. MySQL · 引擎特性 · InnoDB 事务子系统介绍

    http://mysql.taobao.org/monthly/2015/12/01/ 前言 在前面几期关于 InnoDB Redo 和 Undo 实现的铺垫后,本节我们从上层的角度来阐述 InnoD ...

随机推荐

  1. Autofac Container 的简单的封装重构

    为了使用方便,对Autofac container的简单封装,记录如下,备以后用或分享给大家,欢迎讨论! using Autofac; using Autofac.Core.Lifetime; usi ...

  2. Java的堆(Heap)和栈(Stack)的区别

    Java中的堆(Heap)是一个运行时数据区,用来存放类的对象:栈(Stack)主要存放基本的数据类型(int.char.double等8种基本数据类型)和对象句柄. 例1 int a=5; int ...

  3. [转]PHP编码规范

    注:这是10年前的一篇PHP编码规范,最早发布于清华水木BBS,现在好像都找不到完整的版本了,但至今看起来仍是非常有参考意义.个人会根据经验做一些调整.文中对于命名一段的描述极大的曾启发了个人的编程体 ...

  4. java Servlet(续)

    一.web.xml配置 由于需用户需要通过URL访问架设好的Servlet,所以我们必须将servlet映射到一个URL地址上, 比如上面的配置文件我们就可以通过:http://localhost:8 ...

  5. inflate的基本用法,类似于findviewbyId

    Inflate()作用就是将xml定义的一个布局找出来,但仅仅是找出来而且隐藏的,没有找到的同时并显示功能.最近做的一个项目就是这一点让我迷茫了好几天. android上还有一个与Inflate()类 ...

  6. GJM : Unity3D - UI - UI边缘流光特效小技巧 [转载]

    感谢您的阅读.喜欢的.有用的就请大哥大嫂们高抬贵手"推荐一下"吧!你的精神支持是博主强大的写作动力以及转载收藏动力.欢迎转载! 版权声明:本文原创发表于 [请点击连接前往] ,未经 ...

  7. C#操作Excel的函数

    对于Excel的数据处理功能,大家都已经了解. 我们经常需要将数据导入到Excel,或直接打开Excel文档,读写文件操作,这需要用到ExcelHelper类,有了这个类,这些操作大大的减少我们工作量 ...

  8. Jsp九大内置对象及其作用域

    本文主要介绍Jsp九大内置对象及其作用域,着重介绍几个常用的,希望对初学者有所帮助. 1 Jsp九大内置对象 2 详细介绍 2.1 request javax.servlet.http.HttpSer ...

  9. 使用 HTML5 WebGL 实现逼真的云朵效果

    这里给大家展示一个使用 HTML5 WebGL 实现超逼真的云朵效果.WebGL 是一项在网页浏览器呈现3D画面的技术,有别于过去需要安装浏览器插件,通过 WebGL 的技术,只需要编写网页代码即可实 ...

  10. store.js - 轻松实现本地存储(LocalStorage)

    store.js 是一个兼容所有浏览器的 LocalStorage 包装器,不需要借助 Cookie 或者 Flash.store.js 会根据浏览器自动选择使用 localStorage.globa ...