概述:

在生产环境中,当开启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. 炉石传说 C# 开发笔记 (续)

    炉石传说山寨的工作一直在进行着,在开发过程中深深体会到,对于业务的理解和整个程序的架构的整理远比开发难得多. 在开发过程中,如果你的模型不合理,不准确,很有可能造成代码的混乱,冗余,难以维护和扩展性比 ...

  2. 富客户端 wpf, Winform 多线程更新UI控件

    前言 在富客户端的app中,如果在主线程中运行一些长时间的任务,那么应用程序的UI就不能正常相应.因为主线程要负责消息循环,相应鼠标等事件还有展现UI. 因此我们可以开启一个线程来格外处理需要长时间的 ...

  3. Python 3 利用 subprocess 实现管道( pipe )交互操作读/写通信

    这里我们用Windows下的shell来举例: from subprocess import * #因为是举例,就全部导入了 为了方便你理解,我们用一个很简单的一段代码来说明: 可以看见我们利用Pop ...

  4. Array数组基础

    数组的定义 数组(array)是按次序排列的一组值,单个值称为元素,它们的位置都有编号(从0开始).整个数组用方括号表示. var arr = ['a', 'b', 'c']; 上面代码中的a.b.c ...

  5. CentOS 6.5 搭建 LAMP 环境

    目录 第一步: 安装和配置Apache服务器 第二步: 设置.绑定站点目录文件 第三步: 安装MYSQL数据库 第四步: 安装PHP环境 总结 >>Begin: 第一步: 安装和配置Apa ...

  6. linux下安装redis的详细过程

    先安装一些组件: yum -y install gcc gcc-c++ libstdc++-devel 下载并安装: # wget http://download.redis.io/releases/ ...

  7. velocity-tools-beta1.jar与velocity-tools.jar不兼容

    今天在升级了某些依赖jar的版本启动一项目后,velocity中的有些定义在common.vm中变量居然变成了null,没能include进来导致,而没升级的环境是ok的,经过反查,最后发现是将vel ...

  8. javax.el.PropertyNotFoundException: Property 'name' not found on type java.lang.String

    javax.el.PropertyNotFoundException: Property 'name' not found on type java.lang.String javax.el.Bean ...

  9. Julius JS – 最流行的网页语音识别库

    JuliusJS 是用于在网页中的语音识别库.这是 Julius(由日本京都大学和日本IPA联合开发的一个实用高效双通道的大词汇连续语音识别引擎)的 JavaScript 实现.它实时侦听用户的语音并 ...

  10. PHP清理跨站XSS xss_clean 函数 整理自codeigniter Security

    PHP清理跨站XSS xss_clean 函数 整理自codeigniter Security 由Security Class 改编成函数xss_clean 单文件直接调用.BY吠品. //来自cod ...