导致crash原因

数据库先是被置为只读,然后过了一段时间,MySQL直接Crash掉了 发生Crash时MySQL的error日志中打印了以下内容:

SEMAPHORES 
----------
OS WAIT ARRAY INFO: reservation count 1246555
--Thread 140363572082432 has waited at row0upd.cc line 2354 for 253.00 seconds the semaphore:
X-lock (wait_ex) on RW-latch at 0x7fa949340740 created in file buf0buf.cc line 1069
a writer (thread id 140363572082432) has reserved it in mode wait exclusive
number of readers 1, waiters flag 1, lock_word: ffffffffffffffff
Last time read locked in file btr0sea.cc line 931
Last time write locked in file /export/home/pb2/build/sb_0-17068951-1447697721.44/mysql-5.6.28/storage/innobase/row/row0upd.cc line 2354

根据日志中我们可以看到,线程140363572082432要对记录上一个X锁,但是等待0x7fa949340740线程的RW-latch的释放

我们在向下看查询到如下信息(涉及到用户信息 谓词就用xxx代替):

173 lock struct(s), heap size 30248, 7925 row lock(s), undo log entries 7924 
MySQL thread id 5709783, OS thread handle 0x7fa8f0da7700, query id 92213034 10.23.163.54 citicqyh updating
update TB_DEPARTMENT_INFO set TOTAL_USER=1 where ID='ac84f17e-82d3-4519-a1da-0d5a5a835d44'
---TRANSACTION 53065242, ACTIVE 313 sec fetching rows, thread declared inside InnoDB 2081
mysql tables in use 2, locked 0
MySQL thread id 5428690, OS thread handle 0x7fa8f0136700, query id 92213061 10.23.163.55 citicqyh Sending data
SELECT COUNT(DISTINCT r.user_id) FROM TB_DEPARTMENT_INFO d left join tb_qy_user_department_ref r on r.department_id = d.id WHERE d.org_id = 'xxx' AND (d.dept_full_name LIKE 'xxx%' or d.dept_full_name = 'xxx
Trx read view will not see trx with id >= xxx, sees < xxx

......还有很多select语句省略

根据上面信息我们去数据库中查看了这些select语句,发现执行计划都是全表扫描。 
首先数据库变成了只读,最后数据库crash了,crash输出的信息如下:

END OF INNODB MONITOR OUTPUT 
============================

InnoDB: ###### Diagnostic info printed to the standard error stream
InnoDB: Error: semaphore wait has lasted > 600 seconds
InnoDB: We intentionally crash the server, because it appears to be hung.
7fa8f9580700 InnoDB: Assertion failure in thread 140363714529024 in file srv0srv.cc line 1754
InnoDB: We intentionally generate a memory trap.
InnoDB: Submit a detailed bug report to 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
InnoDB: about forcing recovery.

InnoDB: Error: semaphore wait has lasted > 600 seconds 提示600秒没有响应 数据库选择了Crash 强制重启

原因分析

从报错信息来看:

  1. update语句需要在记录上面加X锁,但是必须等待RW-Latch的释放
  2. 由于有大量的select语句是全表扫描,一直占用latch没有释放,update迟迟竞争不到RW-latch
  3. Innodb 的Diagnostic线程检查到RW-Latch等待超过了600秒还没有返回,认为系统出现了严重问题,于是触发了MySQL服务的Crash。

这里首先需要补充一下Latch的概念:Latch在MySQL中是用于保护高速缓冲区中共享数据的,举个例子: 
当我们执行select时,数据是缓存在buffer pool中的,多个线程并发访问或者修改这个数据必然需要一个并发控制机制,这个就是Latch

大家知道,数据库要访问的数据都必须先存在缓存中,而缓存一般比磁盘空间要小,数据缓存使用hash表来记录数据页是否在内存中。在Oracle中的并发控制比较精细:首先会对hash桶加latch,并根据hash桶查找对应的数据并加上pin,然后释放Latch。而MySQL相对没有控制得这么精细,对应的RW-Latch在errlog中说的很清楚,该RW-Latch是在buf0buf.cc的1069行创建的 
RW-latch at 0x7fa949340740 created in file buf0buf.cc line 1069

对应的代码摘录如下:

rw_lock_create(PFS_NOT_INSTRUMENTED, &block->lock, SYNC_LEVEL_VARYING); 

跟踪源码,知道这个Latch是MySQL在数据库启动,初始化 innodb_buffer_pool时,将Latch创建好的。对应的函数调用过·程:

buf_pool_init_instance()->buf_chunk_init()->buf_block_init() 

正是由于这个RW-Latch被长时间占用了,其他的线程一直竞争不到,才导致了这个问题

这类问题的发生多数都是因为SQL写的不好,在表上面进行了大量的全表扫描占用了大量的Latch,解决方案就是避免SQL长时间占用latch:

  1. 修改select查询避免全表扫描,避免latch长期被占用
  2. 适当的加索引,让select执行更快,也避免一个select锁的数据更少
  3. 适当加大buffer pool instance,每个buffer pool都有自己独立的Latch,避免latch竞争。

latch锁

什么是latch锁

从以上案例,我们知道是latch锁导致mysql crash了,那什么是latch锁?

latch 称为闩锁,只作用于内存数据结构,latch 是轻量级的锁,线程通常只在临界区内读写共享内存数据结构时持有 latch,因此 latch 的锁定时间一般很短并且频繁使用,latch 的获取和释放需要尽量小的消耗和更好的性能。在Innodb存储引擎中,latch又可以分为mutex(互斥量)和RW-Lock(读写锁)。
innodb使用show engine innodb mutex进行查看latch

mutex:互斥量;有时候有些资源需要共享和并发,但是又不是分频繁,所以向操作系统申请一个mutex,mutex都是排他的。
rw-latch: 读写锁

latch锁特性

1、不排队
2、spin,一个线程想获得一个锁,但是该锁已被另一线程持有,进行spin(空转随机时间)占用cpu间接性的等待锁的释放,然后获取去进行相关操作。
3、os waits:sleep,spin多次仍然spin
4、cpu繁忙,latch争用

latch争用的现象和过程

latch争用现象

1、latch争用会表现为cpu繁忙

2、latch争用没有排队,等一段随机的时间再回来看一看

latch争用过程

Latch导致MySQL Crash _数据

1、链上有一个链的保护机制latch,小内存结构;

2、这时候有读的线程A上来要读取链,这个时候这个管理就变成r(读锁),当在链上找到数据页的时候(读),一找到就释放读锁;

3、B上来也要读取,这个时候一看是r,读锁是可以共享的,它也对链进行访问读取;

4、C上来要修改链中的两个块的内容,一看是r,r和w是互斥的,不能同时进行,要么

(1)主动要求退出cpu;
(2)空占着cpu资源(执行一段空代码,loop,隔一段时间看看A和B有没有使用完(spin),但是在这个过程中因为C没有排队等待,所以可能在等待的过程中又有其他的线程上来霸占链(不排队的坏处),如果执行多次仍这样,可能就sleep,退出cpu了,sleep,产生os waits)。

5、为什么空占(害怕os看它闲着把它强行拖出去)

6、等(因为它知道A和B占用资源时间比较短,就是遍历一条链的时间非常短)

latch什么时候会产生严重的争用

1、异常SQL:往往意味着latch争用

大量的物理读:修改链

大量的内存读:遇到修改链的冲突

2、内存访问频繁(不停找),其实也是异常SQL造成的。

3、list太长

链上挂10000个块,被持有的几率太大……

所以,有时候会增加instance的数量,把大pool切成小的pool,让list链变的短一些。

mysql> show variables like 'i%instances';
+------------------------------+-------+
| Variable_name | Value |
+------------------------------+-------+
| innodb_buffer_pool_instances | 1 |
+------------------------------+-------+
1 row in set (0.00 sec)

如何监控latch争用情况

mysql> show engine innodb status\G
……
SEMAPHORES
----------
OS WAIT ARRAY INFO: reservation count 23
OS WAIT ARRAY INFO: signal count 14
RW-shared spins 0, rounds 73, OS waits 5
RW-excl spins 0, rounds 1114, OS waits 5
RW-sx spins 0, rounds 0, OS waits 0
Spin rounds per wait: 73.00 RW-shared, 1114.00 RW-excl, 0.00 RW-sx

rounds:表示spin一次空转多少圈,也就是返回来询问的次数

OS waits:表示sleep,当突然增长比较快时,说明latch争用比较严重

1、如果OS waits值比较高,说明出现latch争用,异常SQL

2、获取latch的代价:73.00 RW-shared, 1114.00 RW-excl

查看锁类型show engine innodb mutex;
+--------+-----------------------------+----------+
| Type | Name | Status |
+--------+-----------------------------+----------+
| InnoDB | rwlock: dict0dict.cc:2687 | waits=1 |
| InnoDB | rwlock: dict0dict.cc:1184 | waits=13 |
| InnoDB | rwlock: log0log.cc:844 | waits=35 |
| InnoDB | sum rwlock: buf0buf.cc:1457 | waits=4 |
+--------+-----------------------------+----------+
4 rows in set (0.16 sec)

如何降低latch争用

1、优化SQL,降低对内存读的数量,效果比较明显。
2、增加innodb_buffer_pool_instances的数量。对于具有大内存的64位系统,可以将缓冲池拆分成多个实例(默认8个),把需要缓冲的数据hash到不同的缓冲池中,这样可以并行的内存读写,以最大限度地减少并发操作中内存结构的争用。