6

Latch导致MySQL Crash

 2 years ago
source link: https://blog.51cto.com/u_13874232/5659289
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Latch导致MySQL Crash

精选 原创

进击的CJR 2022-09-07 16:45:05 博主文章分类:MySQL ©著作权

文章标签 mysql 数据 数据库 文章分类 MySQL 数据库 yyds干货盘点 阅读数216

导致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到不同的缓冲池中,这样可以并行的内存读写,以最大限度地减少并发操作中内存结构的争用。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK