DBA

记一次MySQL Crash处理

2020-12-11  本文已影响0人  mysia

在MySQL 5.7的高并发场景下,有可能会触发MySQL5.7的Bug,导致宕机,临时的紧急修复方法可针对实例进行参数的修改。Bug的详细信息,请参考Bug#77588Bug#100771

Bug的详细信息:

2020-12-10 18:12:42 7f5e54b3e700  InnoDB: Assertion failure in thread 140043124729600 in file ha_innodb.cc line 10730
InnoDB: Failing assertion: index->table->stat_initialized
InnoDB: We intentionally generate a memory trap.
InnoDB: Submit a detailed bug report to [http://bugs.mysql.com](http://bugs.mysql.com/).
InnoDB: If you get repeated assertion failures or crashes, even
InnoDB: immediately after the mysqld startup, there may be
InnoDB: corruption in the InnoDB tablespace. Please refer to
InnoDB: [http://dev.mysql.com/doc/refman/5.6/en/forcing-innodb-recovery.html](http://dev.mysql.com/doc/refman/5.6/en/forcing-innodb-recovery.html)
InnoDB: about forcing recovery.
18:12:42 UTC - mysqld got signal 6 ;
This could be because you hit a bug. It is also possible that this binary
or one of the libraries it was linked against is corrupt, improperly built,
or misconfigured. This error can also be caused by malfunctioning hardware.
We will try our best to scrape up some info that will hopefully help
diagnose the problem, but since we have already crashed, 
something is definitely wrong and this may fail.

key_buffer_size=33554432
read_buffer_size=131072
max_used_connections=6139
max_threads=7000
thread_count=4112
connection_count=4109
It is possible that mysqld could use up to 
key_buffer_size + (read_buffer_size + sort_buffer_size)*max_threads = 2810236 K  bytes of memory
Hope that's ok; if not, decrease some variables in the equation.

Thread pointer: 0xce7b1b30
Attempting backtrace. You can use the following information to find out
where mysqld died. If you see no messages after this, something went
terribly wrong...
stack_bottom = 7f5e54b3de18 thread_stack 0x40000
/usr/sbin/mysqld(my_print_stacktrace+0x35)[0x8db795]
/usr/sbin/mysqld(handle_fatal_signal+0x494)[0x664dc4]
/lib64/libpthread.so.0[0x3280a0f710]
/lib64/libc.so.6(gsignal+0x35)[0x3280632625]
/lib64/libc.so.6(abort+0x175)[0x3280633e05]
/usr/sbin/mysqld[0x9893b4]
/usr/sbin/mysqld[0x98baab]
/usr/sbin/mysqld(_ZN7handler7ha_openEP5TABLEPKcii+0x3e)[0x5a8a1e]
/usr/sbin/mysqld(_Z21open_table_from_shareP3THDP11TABLE_SHAREPKcjjjP5TABLEb+0x684)[0x76ded4]
/usr/sbin/mysqld(_Z10open_tableP3THDP10TABLE_LISTP18Open_table_context+0x1e3)[0x69cc13]
/usr/sbin/mysqld(_Z11open_tablesP3THDPP10TABLE_LISTPjjP19Prelocking_strategy+0xc9f)[0x69f49f]
/usr/sbin/mysqld(_Z30open_normal_and_derived_tablesP3THDP10TABLE_LISTj+0x4f)[0x69f5cf]
/usr/sbin/mysqld[0x6df2e1]
/usr/sbin/mysqld(_Z21mysql_execute_commandP3THD+0x1553)[0x6e1c63]
/usr/sbin/mysqld(_Z11mysql_parseP3THDPcjP12Parser_state+0x3c0)[0x6e5790]
/usr/sbin/mysqld(_Z16dispatch_command19enum_server_commandP3THDPcj+0x152b)[0x6e6d6b]
/usr/sbin/mysqld(_Z24do_handle_one_connectionP3THD+0xcf)[0x6b32bf]
/usr/sbin/mysqld(handle_one_connection+0x47)[0x6b33e7]
/usr/sbin/mysqld(pfs_spawn_thread+0x12a)[0x960b4a]
/lib64/libpthread.so.0[0x3280a079d1]
/lib64/libc.so.6(clone+0x6d)[0x32806e88fd]

Trying to get some variables.
Some pointers may be invalid and cause the dump to abort.
Query (7f5de8f1fac0): is an invalid pointer
Connection ID (thread ID): 246446
Status: NOT_KILLED

解决方法:

set global innodb_stats_auto_recalc=off;
set global innodb_stats_persistent=off;
set global innodb_spin_wait_delay=6;
set global innodb_sync_spin_loops=30;
set global table_definition_cache=3000;
  1. innodb_stats_auto_recalc
    参数含义:是否自动触发更新统计信息
    触发条件:
    a. 表修改时,确认变化的数据是否超过10%,超过自动收集统计信息;
    b. 表的索引统计信息是持久化存储;
    默认值:ON

    详细解读:
    由于自动统计信息重新计算(发生在后台)是异步,在运行影响超过10%的表的DML操作时(即使innodb_stats_auto_recalc启用后),可能不会立即重新计算统计信息 。在某些情况下,统计重新计算可能会延迟几秒钟(10s)。如果在更改表的重要部分之后立即需要最新统计信息,请运行ANALYZE TABLE以启动统计信息的同步(前台)重新计算。

    如果禁用了innodb_stats_auto_recalc,请在对索引列进行实质性更改后,通过为每个适用的表发出ANALYZE TABLE语句来确保统计信息的准确性。

    在表上添加索引或者添加删除索引中的列时,将自动计算索引统计信息并将其添加到innodb_index_stats表,不受innodb_stats_auto_recalc的值影响。

  2. innodb_stats_persistent
    参数含义:是否启用持久化统计信息功能
    默认值:ON

    详细解读:
    持久化统计信息功能通过将统计信息存储到磁盘并使其在服务器重新启动期间保持不变来提高执行计划的稳定性,以便优化器更有可能每次为给定查询做出一致的选择。

  3. innodb_spin_wait_delay
    参数含义:每次互斥锁自旋的等待的时间
    默认值:6

  1. innodb_sync_spin_loops
    参数含义:获取互斥锁的自旋次数
    默认值:30

  2. table_definition_cache
    参数含义:数据库实例打开表定义文件的数量,应大于实际表数量,最大值为2000
    默认值:-1

关于innodb_spin_wait_delay和innodb_sync_spin_loops,涉及到InnoDB内核的一些知识点,这里做一些介绍。

InnoDB 中的mutex 和 rw_lock 在早期的版本都是通过系统提供的cas,tas 语义自己进行实现,并没有使用pthread_mutex_t,pthread_rwlock_t,这样实现的好处在于便于统计,以及为了性能考虑,还有解决早期操作系统的一些限制。

大概的原理就是,在mutex_enter 之后,在spin 的次数超过 innodb_sync_spin_loops=30 每次最多 innodb_spin_wait_delay=6如果还没有拿到Mutex,会主动yield() 这个线程,然后wait 在自己实现的wait array 进行等待。

这里每次spin 时候,等待的时候执行的是ut_delay,在ut_delay 中是执行 "pause" 指定,当innodb_spin_wait_delay = 6 的时候,在当年100MHz Pentium cpu,这个时间最大是1us。

wait array 也是InnoDB 实现的一种cond_wait 的实现,早期的MySQL 需要wait array 是因为操作系统无法提供超过100000 event,因此wait array 在用户态去进行这些event 维护,但是到了MySQL 5.0.30 以后,大部分操作系统已经能够处理100000 event,那么现在之所以还需要 wait array,主要是为了统计。

在wait array 的实现里面其实有一把大wait array mutex,是一个pthread_mutex_t,然后在wait array 里面的每一个wait cell 中,包含了os_event_t,wait 的时候调用了os_event_wait_low(),然后在 os_event_t 里面也包含了一个mutex,因此在一次wait 里面就有可能调用了两次pthread_mutex_t 的wait。

并且在os_event_t 唤醒的机制中是直接通过pthread_cond_boradcast(),当有大量线程等待在一个event 的时候, 会造成很多无谓的唤醒。

但是现有的mutex 实现会导致cpu 利用过高,差不多比使用pthread mutex 高16%,并且上下文切换也会更高。

主要的原因是:

  1. 因为Mutex 的唤醒在os_event 里面,os_event 实现中,如果需要执行唤醒操作,那么需要执行pthread_cond_boradcast() 操作,需要把所有等待的pthread 都唤醒,而不是只唤醒一个;
  2. 在wait array 的实现中,需要有一个全局的pthread_mutex_t 保护 sync array;
  3. 在默认的配置中,innodb_spin_wait_delay=6 是ut_delay 执行1us,innodb_sync_spin_loops=30 会执行30次,那么每次mutex 有可能都需要spin 30us;

不同场景需要的mutex 是不一样的,比如buffer pool 上面的page 的mutex 希望的就是一直spin。有些mutex 其实则是希望立刻就进入等待,只用使用这些mutex 的使用者知道接下来哪一个策略更合适。操作系统提供了futex 可能比InnoDB 自己通过wait array 的实现方式,对于通知机制而言会做的更好。

总结了现有的 mutex 实现存在的问题:

  1. 只有自己实现的ib_mutex_t, 并没有支持futex 的实现;
  2. 所有的ib_mutex_t 的行为都是一样的,通过两个变量 innodb_spin_wait_delay(控制在Test 失败以后, 最多会delay 的时间),innodb_sync_spin_loops(控制spin 的次数)。不可以对某一个单独的ib_mutex_t 设置单独的wait + loop 次数;
  3. 所有的ib_mutex_t 由两个全局的变量控制,因为mutex 在尝试了innodb_sync_spin_loops 次以后,会等待在一个wait array 里面的一个wait cell 上,所有的wait cell 都会注册到一个叫wait array 的队列中进行等待;

在 InnoDB 8.0 的代码中总共实现了4种mutex 的实现方式,2种的策略:

  1. TTASFutexMutex 是spin + futex 的实现,在mutex_enter 之后,会首先spin 然后在futex 进行wait;
  2. TTASMutex 全spin 方式实现,在spin 的次数超过 innodb_sync_spin_loops=30 每次最多 innodb_spin_wait_delay=6us 以后,会主动yield() 这个线程,然后通过TAS(test and set 进行判断) 是否可以获得;
  3. OSTrackMutex,在系统自带的mutex 上进行封装,增加统计计数等等;
  4. TTASEevntMutex,InnoDB 一直使用的自己实现的Mutex;

同时在8.0 的实现中定义了两种策略,GenericPolicy和BlockMutexPolicy。这两种策略主要的区别在于在show engine innodb mutex 的时候不同的统计方式。

BlockMutexPolicy 用于统计所有buffer pool 使用的mutex,因此该Mutex 特别多。如果每一个bp 单独统计,浪费大量的内存空间,因此所有bp mutex 都在一起统计,事实上buffer pool 的rw_lock 也是一样。

GenericPolicy 用于除了buffer pool mutex 以外的其他地方。

目前InnoDB 里面都是使用 TTASEventMutex。只不过buffer pool 的mutex 使用的是 BlockMutexPolicy,而且他的mutex 使用的是 GenericPolicy,不过从目前的代码来看,也只是统计的区别而已。

但是从目前来看,不同场景使用不同的mutex,Buffer pool 使用 TTASMutex 实现,其他mutex 使用 TTASEventMutex,并且新加入的 TTASFutexMutex,也就是spin + futex 的实现方式,其实也不是默认使用的,而且wai array 的实现方式也并没有改动。

上一篇下一篇

猜你喜欢

热点阅读