• 售前

  • 售后

热门帖子
入门百科

MySQL删除表时I/O错误的缘故起因分析与解决

[复制链接]
喜欢头像那女纸 显示全部楼层 发表于 2021-10-26 14:11:02 |阅读模式 打印 上一主题 下一主题
问题现象

近来使用sysbench测试MySQL,由于测试时间较长,写了一个脚本按prepare->run->cleanup的顺序在背景跑着。跑完后察看日志发现一个问题,MySQL服务的错误日志中出现多条类似以下信息的报错:
  1. [ERROR] InnoDB: Trying to do I/O to a tablespace which does not exist. I/O type: read, page: [page id: space=32, page number=57890], I/O length: 16384 bytes。
复制代码
看起来是I/O出现了错误,但MySQL进程并未崩溃,sysbench客户端也没有报错。
发现问题过程

根据报错的时间记载以及脚本输出的各个阶段的时间点对比,确定了当时脚本正在实行的下令为:
  1. sysbench --tables=100 --table-size=4000000 --threads=50 --mysql-db=sbtest --time=300 oltp_delete cleanup
复制代码
重新手动实行一遍这个用例,却没有再出现同样的情况。但是用脚本实行却依然可以或许发现这个错误信息。开端怀疑是run和cleanup之间不能隔断太久才会触发这个问题。由于实行一遍100G数据量的时间较长,重今世价较大,先尝试缩减用例数据量。将—table-size=4000000修改为2000000,此时实行脚本,又不会触发这个问题了,末了将—table-size=3000000可以稳定触发又能淘汰部分重现时间。为了确认是否隔断太长会导致不能复现,修改脚本在run和cleanup两个阶段之间sleep 10秒,果然不会触发这个错误信息。修改为sleep 5秒则还能触发,不外报错条数已有所淘汰。
问题观察

察看对应版本mysql5.7.22的代码,发现这个报错只有一个位置:fil0fil.cc文件的第5578行fil_io()函数内。 直接使用gdb调试,在这个位置加上断点,并实行可复现的脚本,得到以下堆栈:
  1. (gdb) bt
  2. #0 fil_io (type=..., sync=sync@entry=false, page_id=..., page_size=..., byte_offset=byte_offset@entry=0, len=16384, buf=0x7f9ead544000, message=message@entry=0x7f9ea8ce9c78) at mysql-5.7.22/storage/innobase/fil/fil0fil.cc:5580
  3. #1 0x00000000010f99fa in buf_read_page_low (err=0x7f9ddaffc72c, sync=<optimized out>, type=0, mode=<optimized out>, page_id=..., page_size=..., unzip=true) at mysql-5.7.22/storage/innobase/buf/buf0rea.cc:195
  4. #2 0x00000000010fc5fa in buf_read_ibuf_merge_pages (sync=sync@entry=false, space_ids=space_ids@entry=0x7f9ddaffc7e0, page_nos=page_nos@entry=0x7f9ddaffc7a0, n_stored=2) at mysql-5.7.22/storage/innobase/buf/buf0rea.cc:834
  5. #3 0x0000000000f3a86c in ibuf_merge_pages (n_pages=n_pages@entry=0x7f9ddaffce30, sync=sync@entry=false) at mysql-5.7.22/storage/innobase/ibuf/ibuf0ibuf.cc:2552
  6. #4 0x0000000000f3a94a in ibuf_merge (sync=false, sync=false, n_pages=0x7f9ddaffce30) at mysql-5.7.22/storage/innobase/ibuf/ibuf0ibuf.cc:2656
  7. #5 ibuf_merge_in_background (full=full@entry=false) at mysql-5.7.22/storage/innobase/ibuf/ibuf0ibuf.cc:2721
  8. #6 0x000000000102bcf4 in srv_master_do_active_tasks () at mysql-5.7.22/storage/innobase/srv/srv0srv.cc:2132
  9. #7 srv_master_thread (arg=<optimized out>) at mysql-5.7.22/storage/innobase/srv/srv0srv.cc:2383
  10. #8 0x00007fa003eeddc5 in start_thread () from /lib64/libpthread.so.0
  11. #9 0x00007fa002aab74d in clone () from /lib64/libc.so.6
复制代码
很显着这是背景线程在做insert buffer merge操纵。此时发现space->stop_new_ops为true,也就是要处置惩罚的页面所属的space正在被删除。为什么会去操纵正在被删除的space呢?这必要观察下insert buffer功能、insert buffer merge的流程以及删除表的流程。
insert buffer背景知识

insert buffer是一种特别的数据结构(B+ tree),当辅助索引页面不在缓冲池中时,它会将更改缓存起来,稍后在页面被其他读取操纵加载到缓冲池中时归并。MySQL最初引进这个功能的时间只能缓存insert操纵,所以叫做insert buffer,现在这些操纵可以是 INSERT, UPDATE, or DELETE(DML),所以改叫做change buffer了(本文依然以insert buffer描述),但源码中依然以ibuf作为标识。这个功能把若干对同一页面的更新缓存起来,归并为一次性更新操纵,淘汰了IO,并转化随机IO为顺序IO,如许可以避免随机IO带来性能消耗,进步数据库的写性能。
相干insert buffer merge逻辑

当buffer page读入buffer pool时,就会进行insert buffer merge。紧张有几个场景会出现merge过程:
      
  • 当页面被读入缓冲池时,读取完成后先辈行ibuf的merge,然后页面才可用;  
  • merge操纵作为背景任务实行。 innodb_io_capacity参数可设置InnoDB背景任务每次merge过程的页面数上限;  
  • 在崩溃规复期间,当索引页被读入缓冲池时,将实行对应页的insert buffer merge;  
  • insert buffer具有长期性,系统崩溃不会导致它失效。重启后,insert buffer merge操纵将规复正常;  
  • 服务器关闭时可使用—innodb-fast-shutdown = 0逼迫进行ibuf的完全归并。
我们这次的问题很显着属于第二种情况。innodb主线程(svr_master_thread)会每隔一秒自动进行一次insert buffer的merge操纵。先判断已往1s之内服务器是否发生过运动(插入元组到页面、undo表上的行操纵等),假如发生过,则merge的最大页面数为innodb_io_capacity设定的5%。假如没有则merge的最大页面数为innodb_io_capacity设定的值。
innodb主线程(svr_master_thread)merge的主流程如下:
      
  • 主线程从ibuf树的叶子节点读取页号和space号,并记载到一个二元数组中(未加锁);  
  • 主线程对二元组中space进行检测是否在表空间缓存中,如不在,说明已经被删除了,删除对应ibuf的记载;  
  • 主线程判断是否对一个正在删除的space进行异步读取操纵,假如是,报错,并删除对应ibuf的记载,转到过程2继续下一个数组元素的判断;  
  • 假如齐备判断正常,主线程发出async io请求,async读取必要被merge的索引页面;  
  • I/O handler 线程,在接受到完成的async I/O之后,进行merge操纵;  
  • 进行merge的时间调用fil_space_acquire对space->n_pending_ops进行自增。避免删除操纵并发;  
  • 实行完毕后调用fil_space_release对space->n_pending_ops进行自减。
相干删除表的逻辑

      
  • 对fil_system->mutex加锁,设置sp->stop_new_ops = true,标志space正在删除,不允许对它进行新操纵,然后对fil_system->mutex解锁;  
  • 对fil_system->mutex加锁,检测space->n_pending_ops,对fil_system->mutex解锁。假如检测到大于0,意味着还有依赖的操纵未完成,睡眠20ms后重试;  
  • 对fil_system->mutex加锁,检测space->n_pending_flushes和(*node)->n_pending ,对fil_system->mutex解锁。假如检测到大于0,意味着还有依赖的I/O未完成,睡眠20ms后重试;  
  • 此时认为已经没有辩论的操纵了,刷出全部脏页面或删除全部给定的表空间的页面;  
  • 从表空间缓存删除指定space的记载;  
  • 删除对应数据文件。
问题结论

情况很明白了,主线程获取ibuf的(space,page)的过程与删除操纵实行的过程并没有锁包管互斥,只有async I/O完成之后的merge操纵与删除操纵才有互斥。假如背景线程开始ibuf merge并已经实行过了第2步的检测,但还没有实行到第3步检测,此时用户线程开始做删除表的操纵,并设置好stop_new_ops标志但还没有实行到第5步删除表空间缓存,就会出现这个错误信息。两线程的交互如下图所示:

不出意外的话,在打中断点时肯定有线程在实行对应表的删除操纵。果然我们可以发现如下堆栈:
  1. Thread 118 (Thread 0x7f9de0111700 (LWP 5234)):
  2. #0 0x00007fa003ef1e8e in pthread_cond_broadcast@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
  3. #1 0x0000000000f82f41 in broadcast (this=0xd452ef8) at mysql-5.7.22/storage/innobase/os/os0event.cc:184
  4. #2 set (this=0xd452ef8) at mysql-5.7.22/storage/innobase/os/os0event.cc:75
  5. #3 os_event_set (event=0xd452ef8) at mysql-5.7.22/storage/innobase/os/os0event.cc:483
  6. #4 0x00000000010ec8a4 in signal (this=<optimized out>) at mysql-5.7.22/storage/innobase/include/ut0mutex.ic:105
  7. #5 exit (this=<optimized out>) at mysql-5.7.22/storage/innobase/include/ib0mutex.h:690
  8. #6 exit (this=<optimized out>) at mysql-5.7.22/storage/innobase/include/ib0mutex.h:961
  9. #7 buf_flush_yield (bpage=<optimized out>, buf_pool=<optimized out>) at mysql-5.7.22/storage/innobase/buf/buf0lru.cc:405
  10. #8 buf_flush_try_yield (processed=<optimized out>, bpage=<optimized out>, buf_pool=<optimized out>) at mysql-5.7.22/storage/innobase/buf/buf0lru.cc:449
  11. #9 buf_flush_or_remove_pages (trx=<optimized out>, flush=<optimized out>, observer=<optimized out>, id=<optimized out>, buf_pool=<optimized out>) at mysql-5.7.22/storage/innobase/buf/buf0lru.cc:632
  12. #10 buf_flush_dirty_pages (buf_pool=<optimized out>, id=<optimized out>, observer=<optimized out>, flush=<optimized out>, trx=<optimized out>) at mysql-5.7.22/storage/innobase/buf/buf0lru.cc:693
  13. #11 0x00000000010f6de7 in buf_LRU_remove_pages (trx=0x0, buf_remove=BUF_REMOVE_FLUSH_NO_WRITE, id=55, buf_pool=0x31e55e8) at mysql-5.7.22/storage/innobase/buf/buf0lru.cc:893
  14. #12 buf_LRU_flush_or_remove_pages (id=id@entry=55, buf_remove=buf_remove@entry=BUF_REMOVE_FLUSH_NO_WRITE, trx=trx@entry=0x0) at mysql-5.7.22/storage/innobase/buf/buf0lru.cc:951
  15. #13 0x000000000114e488 in fil_delete_tablespace (id=id@entry=55, buf_remove=buf_remove@entry=BUF_REMOVE_FLUSH_NO_WRITE) at mysql-5.7.22/storage/innobase/fil/fil0fil.cc:2800
  16. #14 0x0000000000fe77bd in row_drop_single_table_tablespace (trx=0x0, is_encrypted=false, is_temp=false, filepath=0x7f9d7c209f38 "./sbtest/sbtest25.ibd", tablename=0x7f9d7c209dc8 "sbtest/sbtest25", space_id=55) at mysql-5.7.22/storage/innobase/row/row0mysql.cc:4189
  17. #15 row_drop_table_for_mysql (name=name@entry=0x7f9de010e020 "sbtest/sbtest25", trx=trx@entry=0x7f9ff9515750, drop_db=<optimized out>, nonatomic=<optimized out>, nonatomic@entry=true, handler=handler@entry=0x0) at mysql-5.7.22/storage/innobase/row/row0mysql.cc:4741
  18. #16 0x0000000000f092f3 in ha_innobase::delete_table (this=<optimized out>, name=0x7f9de010f5e0 "./sbtest/sbtest25") at mysql-5.7.22/storage/innobase/handler/ha_innodb.cc:12539
  19. #17 0x0000000000801a30 in ha_delete_table (thd=thd@entry=0x7f9d7c1f6910, table_type=table_type@entry=0x2ebd100, path=path@entry=0x7f9de010f5e0 "./sbtest/sbtest25", db=db@entry=0x7f9d7c00e560 "sbtest", alias=0x7f9d7c00df98 "sbtest25", generate_warning=generate_warning@entry=true) at mysql-5.7.22/sql/handler.cc:2586
  20. #18 0x0000000000d0a6af in mysql_rm_table_no_locks (thd=thd@entry=0x7f9d7c1f6910, tables=tables@entry=0x7f9d7c00dfe0, if_exists=true, drop_temporary=false, drop_view=drop_view@entry=false, dont_log_query=dont_log_query@entry=false) at mysql-5.7.22/sql/sql_table.cc:2546
  21. #19 0x0000000000d0ba58 in mysql_rm_table (thd=thd@entry=0x7f9d7c1f6910, tables=tables@entry=0x7f9d7c00dfe0, if_exists=<optimized out>, drop_temporary=<optimized out>) at mysql-5.7.22/sql/sql_table.cc:2196
  22. #20 0x0000000000c9d90b in mysql_execute_command (thd=thd@entry=0x7f9d7c1f6910, first_level=first_level@entry=true) at mysql-5.7.22/sql/sql_parse.cc:3589
  23. #21 0x0000000000ca1edd in mysql_parse (thd=thd@entry=0x7f9d7c1f6910, parser_state=parser_state@entry=0x7f9de01107a0) at mysql-5.7.22/sql/sql_parse.cc:5582
  24. #22 0x0000000000ca2a20 in dispatch_command (thd=thd@entry=0x7f9d7c1f6910, com_data=com_data@entry=0x7f9de0110e00, command=COM_QUERY) at mysql-5.7.22/sql/sql_parse.cc:1458
  25. #23 0x0000000000ca4377 in do_command (thd=thd@entry=0x7f9d7c1f6910) at mysql-5.7.22/sql/sql_parse.cc:999
  26. #24 0x0000000000d5ed00 in handle_connection (arg=arg@entry=0x10b8e910) at mysql-5.7.22/sql/conn_handler/connection_handler_per_thread.cc:300
  27. #25 0x0000000001223d74 in pfs_spawn_thread (arg=0x10c48f40) at mysql-5.7.22/storage/perfschema/pfs.cc:2190
  28. #26 0x00007fa003eeddc5 in start_thread () from /lib64/libpthread.so.0
  29. #27 0x00007fa002aab74d in clone () from /lib64/libc.so.6
复制代码
办理办法

为buf_read_ibuf_merge_pages、buf_read_page_low、fil_io新增一个参数ignore_missing_space。表现忽略正在删除的space,默认为false,当ibuf_merge_pages调用的时间置为true。在fil_io报错处额外判断该参数是否为true,是则不报错,继续其他流程。
大概直接在buf_read_ibuf_merge_pages调用buf_read_page_low时传入IORequest::IGNORE_MISSING参数。
详细代码参考MariaDB commit:8edbb1117a9e1fd81fbd08b8f1d06c72efe38f44
影响版本

察看相干信息,这个问题是修改Bug#19710564时删除表空间版本引入的。
      
  • MySQL Community Server 5.7.6引入,版本5.7.22尚未修复,版本8.0.0已修复。  
  • MariaDB Server 10.2受影响。MariaDB Server 10.2.9, 10.3.2已修复
优化发起

可优化一下性能:在buf_read_ibuf_merge_pages中记载下出错的space id,循环的时间判断下一个page的space id,假如space id是相同的,直接删除对应ibuf的记载(当前分配的最大space id记载在系统表空间,space id占4个字节,低于0xFFFFFFF0UL,分配时读取系统表空间生存的值,然后加一,具有唯一性)。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习大概工作具有肯定的参考学习价值,假如有疑问大家可以留言交换,谢谢大家对脚本之家的支持。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x

帖子地址: 

回复

使用道具 举报

分享
推广
火星云矿 | 预约S19Pro,享500抵1000!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

草根技术分享(草根吧)是全球知名中文IT技术交流平台,创建于2021年,包含原创博客、精品问答、职业培训、技术社区、资源下载等产品服务,提供原创、优质、完整内容的专业IT技术开发社区。
  • 官方手机版

  • 微信公众号

  • 商务合作