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

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

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

时间: 2024-10-10 08:49:04

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

UNABLE TO PURGE A RECORD(二)

上一篇文章说明了bug出现的原因和原理分析,要修复bug似乎已经水到渠成了,但远没有这么简单,只因为“并发”.要修复问题,首先要做的第一件事情是稳定的复现问题.由于数据库系统是一个并发系统,并且这个bug只有一定的概率出现,更说明了多个线程在一定的执行序列情况下才会出现这个bug.在没有用户请求的情况下,mysql自身的线程就很多,比如主线程,IO线程,监控线程,监听线程等:有用户请求的情况下,还会分配工作线程为用户服务.我们只找和bug相关的线程:purge线程,IO线程,工作线程.Purge

tableView循环引用以及缓存池中的两个常见Bug

一.直接看Bug:unable to dequeue a cell with identifier cell_id - must register a nib or a class for the identifier or connect a prototype cell in a storyboard 二.使用tableView的cell重用机制后cell中不显示detailTextLabel. * *  本文中大量涉及tableView的重新引用机制 *  在tableView上下滑动的时

FNDCPASS Troubleshooting Guide For Login and Changing Applications Passwords

In this Document   Goal   Solution   1. Error Starting Application Services After Changing APPS Password Using FNDCPASS   2. Log In Fails With: You Don't Have Permission To Access /pls/.../fnd_icx_launch.launch On This Server   3. APP-FND-01564: ORAC

[daily][device][bluetooth] 蓝牙怎么办!

去年地摊买的破蓝牙鼠坏掉了.看上微软的Designer Mouse蓝牙鼠,但是买之前我要确认我能不能驱起来. 这款鼠标只支持蓝牙4.0.系统支持windows8以上,不支持xp和windows7. 其他系统支持mac和android,没写linux. 由于之前也没搞过蓝牙设备,所以需要了解一下linux下的蓝牙设备.然后在解决鼠标的问题. 需要解决的问题: 1. archlinux下蓝牙设备的使用与配置 2. 确定T450的蓝牙版本. 3. 驱鼠标. 首先,按照惯例还是读一下万事万灵的archl

2009国家集训队小Z的袜子

莫队算法? 感觉没什么优越性啊?难道就是因为在排序的时候cmp函数的不同?这样做为什么减少时限啊? 我带着疑惑敲了代码,却一直有bug…… 代码: 1 type node=record 2 l,r,id,x,y:int64; 3 end; 4 var a,ans:array[1..55000] of node; 5 s,c,p:array[1..55000] of longint; 6 i,n,m,block,l,r,k:longint; 7 anss:int64; 8 function gcd

openstack新建实例各种报错解决

最近自己装了下Openstack,零基础安装,参照了网上不少教程. 吃了百家饭的后果,就是出现了一堆不明问题...openstack安装比较复杂,很多配置文件,一个地方配置不正确,可能会导致后面的功能不可用. 仅以此文记录安装结束后,启动实例时候遇到的一系列错误及排查过程. BUG 1: No valid host 报错 No valid host was found. There are not enough hosts available. 解决方法 网络节点执行 [[email prote

Unable to execute dex: Multiple dex files define---一个莫名的bug

运行时报错: [2016-07-07 16:26:57 - Dex Loader] Unable to execute dex: Multiple dex files define Lcom/alibaba/fastjson/JSONStreamAware;[2016-07-07 16:26:57 - YoungHeart2.9] Conversion to Dalvik format failed: Unable to execute dex: Multiple dex files defin

【Bug笔记】Unable to load configuration. - Class: java.net.AbstractPlainSocketImpl

最近出了一件怪事,在联网状态下启动Tomcat和没有联网状态下启动Tomcat会出现两种不同的结果:正常启动和报错          于是自己纠结了半天,查找原因是struts.xml中配置的声明方式,我用的jar是struts-2.1.8.jar,而我引用的是2.3的(这个声明方式在互联网上拷贝的,因为参考的是互联网的一些代码例子,往往这里就出错了),它没有在本地形成struts-2.3.dtd文件,所以只有在联网情况下通过http://struts.apache.org/dtds/strut

Hibernate - HHH000352: Unable to release batch statement

这是hibernate的一个bug,具体看https://hibernate.atlassian.net/browse/HHH-11732?attachmentViewMode=list When using stateless session with jdbc batch size we get an HHH000352: Unable to release batch statement.. error in session.close() after rollback: Code:Con