begin!
题目征象
近来运用sysbench测试MySQL,因为测试时刻较长,写了一个剧本按prepare->run->cleanup的递次在背景跑着。跑完后观察日记发明一个题目,MySQL效劳的毛病日记中涌现多条相似以下信息的报错:
[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客户端也没有报错。
发明题目历程
依据报错的时刻纪录以及剧本输出的各个阶段的时刻点对照,肯定了当时剧本正在实行的敕令为:
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调试,在这个位置加上断点,并实行可复现的剧本,获得以下客栈:
(gdb) bt #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 #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 #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 #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 #4 0x0000000000f3a94a in ibuf_merge (sync=false, sync=false, n_pages=0x7f9ddaffce30) at mysql-5.7.22/storage/innobase/ibuf/ibuf0ibuf.cc:2656 #5 ibuf_merge_in_background (full=full@entry=false) at mysql-5.7.22/storage/innobase/ibuf/ibuf0ibuf.cc:2721 #6 0x000000000102bcf4 in srv_master_do_active_tasks () at mysql-5.7.22/storage/innobase/srv/srv0srv.cc:2132 #7 srv_master_thread (arg=<optimized out>) at mysql-5.7.22/storage/innobase/srv/srv0srv.cc:2383 #8 0x00007fa003eeddc5 in start_thread () from /lib64/libpthread.so.0 #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步删除表空间缓存,就会涌现这个毛病信息。两线程的交互以下图所示:
不出不测的话,在打中断点时必定有线程在实行对应表的删除操纵。果真我们可以发明以下客栈:
Thread 118 (Thread 0x7f9de0111700 (LWP 5234)): #0 0x00007fa003ef1e8e in pthread_cond_broadcast@@GLIBC_2.3.2 () from /lib64/libpthread.so.0 #1 0x0000000000f82f41 in broadcast (this=0xd452ef8) at mysql-5.7.22/storage/innobase/os/os0event.cc:184 #2 set (this=0xd452ef8) at mysql-5.7.22/storage/innobase/os/os0event.cc:75 #3 os_event_set (event=0xd452ef8) at mysql-5.7.22/storage/innobase/os/os0event.cc:483 #4 0x00000000010ec8a4 in signal (this=<optimized out>) at mysql-5.7.22/storage/innobase/include/ut0mutex.ic:105 #5 exit (this=<optimized out>) at mysql-5.7.22/storage/innobase/include/ib0mutex.h:690 #6 exit (this=<optimized out>) at mysql-5.7.22/storage/innobase/include/ib0mutex.h:961 #7 buf_flush_yield (bpage=<optimized out>, buf_pool=<optimized out>) at mysql-5.7.22/storage/innobase/buf/buf0lru.cc:405 #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 #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 #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 #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 #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 #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 #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 #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 #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 #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 #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 #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 #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 #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 #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 #23 0x0000000000ca4377 in do_command (thd=thd@entry=0x7f9d7c1f6910) at mysql-5.7.22/sql/sql_parse.cc:999 #24 0x0000000000d5ed00 in handle_connection (arg=arg@entry=0x10b8e910) at mysql-5.7.22/sql/conn_handler/connection_handler_per_thread.cc:300 #25 0x0000000001223d74 in pfs_spawn_thread (arg=0x10c48f40) at mysql-5.7.22/storage/perfschema/pfs.cc:2190 #26 0x00007fa003eeddc5 in start_thread () from /lib64/libpthread.so.0 #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,分派时读取体系表空间保留的值,然后加一,具有唯一性)。
end:关于学问点我就引见到这里了,写的有点快,可能有不足之处,还望多多交换斧正,希望能帮到人人。
相干文章:
mysql1064毛病缘由及解决办法
MySQL常见题目及解决方案
相干视频:
AJAX跨域解决方案:JSONP视频教程
以上就是MySQL中I/O涌现毛病题目缘由及解决方案(附优化发起)的细致内容,更多请关注ki4网别的相干文章!