mysql的purge线程知多少

 

前段时间,作者分析了诸多种类的“云里雾里”的死锁状况,例如因为唯一性索引导致的死锁现象,因为操作空行而导致...



前段时间,作者分析了诸多种类的“云里雾里”的死锁状况,例如因为唯一性索引导致的死锁现象,因为操作空行而导致的死锁现象等,对mysql各种操作对数据页中的行的加锁情况基本解析完毕,但还有一类隐含地对数据行(被删除的行)的操作,就是purge线程。 purge线程的作用,是清理回滚段的同时,把被删除的行也从数据块中清除,实现真正的物理上的删除。  因此,作者就在猜想-----这个purge操作也是行操作,这类操作是否会加锁?如果加锁,那跟用户线程的事务处理中的加锁会不会产生冲突? 如果冲突,那岂不是很难搞定,同时也会莫名奇妙? 因为mysql的管理员没有办法知道purge线程什么时候清理哪个被删除的行,正在清理哪个被删除的行。

希望这些顾虑都是多余的,从作者实际的运维经验中来看----目前为止,还没有发现是因为purge线程的干扰而出现死锁的现象。

好,废话说得太多了,我们还是来实际分析一下,purge线程在数据库中的核心函数吧。

purge线程的前段函数栈如下,可以看到有undo_rec这样的数据结构。 因为数据库的数据页特别多,要清除被删除的页,如果一个一个的找,那代价也太大了,是非常不明智的。 因为所有的变更都有undo, 因此,从undo作为切入点,在清理过期的undo的同时,也将数据页中的被删除的记录一并清除。

下面的栈是通过undo记录的信息,然后去索引上找被删除的记录。

(gdb) bt

#0 row_search_index_entry (index=0x7f793c9524f0, entry=0x7f7970013a10, mode=8194, pcur=0x7f79a1ff9e10, mtr=0x7f79a1ff9f10)

at /data/mysql/mysql-5.7.10/storage/innobase/row/row0row.cc:1094

#1  0x00000000019f95fb in row_purge_remove_sec_if_poss_leaf (node=0x95ce970, index=0x7f793c9524f0, entry=0x7f7970013a10)

at /data/mysql/mysql-5.7.10/storage/innobase/row/row0purge.cc:471

#2  0x00000000019f9a1e inrow_purge_remove_sec_if_poss (node=0x95ce970, index=0x7f793c9524f0, entry=0x7f7970013a10)

at /data/mysql/mysql-5.7.10/storage/innobase/row/row0purge.cc:579

#3  0x00000000019f9dac in row_purge_upd_exist_or_extern_func (thr=0x95b8018, node=0x95ce970,undo_rec=0x95cf780 "3530f")

at /data/mysql/mysql-5.7.10/storage/innobase/row/row0purge.cc:678

#4  0x00000000019fa741 in row_purge_record_func (node=0x95ce970, undo_rec=0x95cf780 "3530f", thr=0x95b8018, updated_extern=false)

at /data/mysql/mysql-5.7.10/storage/innobase/row/row0purge.cc:956

#5  0x00000000019fa891 inrow_purge (node=0x95ce970,undo_rec=0x95cf780 "3530f", thr=0x95b8018) at /data/mysql/mysql-5.7.10/storage/innobase/row/row0purge.cc:1000

#6  0x00000000019fab51 in row_purge_step (thr=0x95b8018) at /data/mysql/mysql-5.7.10/storage/innobase/row/row0purge.cc:1079

#7  0x0000000001983c3d in que_thr_step (thr=0x95b8018) at /data/mysql/mysql-5.7.10/storage/innobase/que/que0que.cc:1051

#8  0x0000000001983e3c in que_run_threads_low (thr=0x95b8018) at /data/mysql/mysql-5.7.10/storage/innobase/que/que0que.cc:1113

#9  0x0000000001983ff2 in que_run_threads (thr=0x95b8018) at /data/mysql/mysql-5.7.10/storage/innobase/que/que0que.cc:1153

#10 0x0000000001a46f3d in srv_task_execute () at /data/mysql/mysql-5.7.10/storage/innobase/srv/srv0srv.cc:2446

#11 0x0000000001a470e0 in srv_worker_thread (arg=0x0) at /data/mysql/mysql-5.7.10/storage/innobase/srv/srv0srv.cc:2496

#12 0x00007f7a959dbdf5 in start_thread () from /lib64/libpthread.so.0

#13 0x00007f7a948a71ad in clone () from /lib64/libc.so.6

真正执行purge操作的,实际上是btr_cur_optimistic_delete  这个函数。

本来以为会发现加锁或者释放锁的动作,找了半天没有找到,最后找个这个关于lock的函数。

下面的函数的作用是将这行上的锁移走,下面我们来解析这个函数。
/*************************************************************//**
Updates the lock table when a record is removed. */

voidlock_update_delete(

/*===============*/

const buf_block_t* block, /*!< in: buffer block containing rec */

const rec_t*  rec) /*!< in: the record to be removed */

{

const page_t* page = block->frame;

ulint  heap_no;

ulint  next_heap_no;

ut_ad(page == page_align(rec));

if (page_is_comp(page)) {  heap_no = rec_get_heap_no_new(rec); next_heap_no = rec_get_heap_no_new(page

+ rec_get_next_offs(rec,

TRUE));

} else {

heap_no = rec_get_heap_no_old(rec);

next_heap_no = rec_get_heap_no_old(page

+ rec_get_next_offs(rec,

FALSE));

}

lock_mutex_enter();

/* Let the next record inherit the locks from rec, in gap mode */

lock_rec_inherit_to_gap(block, block, next_heap_no, heap_no);

/* Reset the lock bits on rec and release waiting transactions */

lock_rec_reset_and_release_wait(block, heap_no);

lock_mutex_exit();

}

我们仅看关键的几行,

heap_no = rec_get_heap_no_new(rec);  计算出(找到)被删除行的heap_no

next_heap_no = rec_get_heap_no_new(page

+ rec_get_next_offs(rec,

TRUE));    计算出下一行的heap_no.

lock_rec_inherit_to_gap(block, block, next_heap_no, heap_no);  然后将这行上的所有的锁,继承给下一行。 因为这样要清除,所有不能再有锁在这行上等待。 实际上,因为这个行是被删除的行,一般情况下,不会有线程对该行加锁。

lock_rec_reset_and_release_wait(block, heap_no);  最后,将这个行上的所有锁释放。

原来purge线程,操作被删除的行之前,不是对被删除的行加锁,而是将目前这个被删除行上的所有锁进行“继承”,将被删除的行上的锁全部交接给下一行。

吼吼,之前的猜测都是错滴。 因为不对被删除的行加锁,而是将这行上的原来的锁继承给下一行, 所以,purge线程很难会影响用户线程的加锁。  放心啦。

关注作者个人公共号,及时获取有关mysql的纯干货原创文章


    关注 数据库随笔


微信扫一扫关注公众号

0 个评论

要回复文章请先登录注册