终于彻底搞清楚了 MySQL spin-lock 之一次 CPU 问题定位过程总结
MySQL 关于 spin lock 的部分代码。如下代码可以看到 MySQL 默认作了 30 次(innodb_sync_spin_loops=30)mutex 检查后,才放弃占用 CPU 资源。
rw_lock_sx_lock_func( // 加 sx 锁函数
{
/* Spin waiting for the lock_word to become free */
os_rmb;
while (i < srv_n_spin_wait_rounds
&& lock->lock_word <= X_LOCK_HALF_DECR) {
if (srv_spin
_wait_delay) {
ut_delay(ut_rnd_interval(
0, srv_spin_wait_delay)); // 加锁失败,调用 ut_delay
}
i++;
}
spin_count += i;
if (i >= srv_n_spin_wait_rounds) {
os_thread_yield(); //暂停当前正在执行的线程对象(及放弃当前拥有的 cup 资源)
} else {
goto lock_loop; //MySQL 关于 spin lock 的部分代码。如下代码可以看到 MySQL 默认作了 30 次(innodb_sync_spin_loops=30)mutex 检查后,才放弃占用 CPU 资源。
os_thread_yield(); //暂停当前正在执行的线程对象(及放弃当前拥有的 cup 资源)
}
...
ulong srv_n_spin_wait_rounds = 30;
ulong srv_spin_wait_delay = 6;
注:上面代码,线程中的 yield()方法说明
yield 多线程版权 Thread.yield()方法作用是:暂停当前正在执行的线程对象(及放弃当前拥有的 cup 资源),并执行其他线程。yield()做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用 yield()的目的是让相同优先级的线程之间能适当的轮转执行。
每次 ut_delay 默认执行 pause 指令 300 次( innodb_spin_wait_delay=6*50)
ut_delay(
/=====/
ulint delay) /*!< in: delay in microseconds on 100 MHz Pentium */
{
ulint i, j;
UT_LOW_PRIORITY_CPU();
j = 0;
for (i = 0; i < delay * 50; i++) {
j += i;
UT_RELAX_CPU();
}
UT_RESUME_PRIORITY_CPU();
return(j);
}
define UT_RELAX_CPU() asm ("pause" )
define UT_RELAX_CPU() asm volatile ("pause")
操作系统中,SYS 和 USER 这两个不同的利用率代表着什么?
操作系统中,SYS 和 USER 这两个不同的利用率代表着什么?或者说二者有什么区别?
简单来说,CPU 利用率中的 SYS 部分,指的是操作系统内核(Kernel)使用的 CPU 部分,也就是运行在内核态的代码所消耗的 CPU,最常见的就是系统调用(SYS CALL)时消耗的 CPU。而 USER 部分则是应用软件自己的代码使用的 CPU 部分,也就是运行在用户态的代码所消耗的 CPU。比如 ORACLE 在执行 SQL 时,从磁盘读数据到 db buffer cache,需要发起 read 调用,这个 read 调用主要是由操作系统内核包括设备驱动程序的代码在运行,因此消耗 CPU 计算到 SYS 部分;而 ORACLE 在解析从磁盘中读到的数据时,则只是 ORACLE 自己的代码在运行,因此消耗的 CPU 计算到 USER 部分。
那么 SYS 部分的 CPU 主要会由哪些操作或是系统调用产生呢?具体如下所示。
1> I/O 操作。比如读写文件、访问外设、通过网络传输数据等。这部分操作一般不会消耗太多的 CPU,因为主要的时间消耗会在 1/O 操作的设备上。比如从磁盘读文件时,主要的时间在磁盘内部的操作上,而消耗的 CPU 时间只占 I/O 操作响应时间的一少部分。只有在过高的并发 I/O 时才可能会使得 SYS CPU 有所增加。
2> 内存管理。比如应用程序向操作系统申请内存,操作系统维护系统可用内存,交换空间换页等。其实与 ORACLE 类似,越大的内存,越频繁的内存管理操作,CPU 的消耗会越高。
3> 进程调度。这部分 CPU 的使用,在于操作系统中运行队列的长短,越长的运行队列,表明越多的进程需要调度,那么内核的负担就越高。
4> 其他,包括进程间通信、信号量处理、设备驱动程序内部的一些活动等等。
什么是用户态?什么是内核态?如何区分?
一般现代 CPU 都有几种不同的指令执行级别。
在高执行级别下,代码可以执行特权指令,访问任意的物理地址,这种 CPU 执行级别就对应着内核态。
而在相应的低级别执行状态下,代码的掌控范围会受到限制。只能在对应级别允许的范围内活动。
举例:
intel x86 CPU 有四种不同的执行级别 0-3,linux 只使用了其中的 0 级和 3 级分别来表示内核态和用户态。
系统调用与 context switch
进程上下文切换,是指从一个进程切换到另一个进程运行。而系统调用过程中一直是同一个进程在运行
系统调用过程通常称为特权模式切换,而不是上下文切换。当进程调用系统调用或者发生中断时,CPU 从用户模式(用户态)切换成内核模式(内核态),此时,无论是系统调用程序还是中断服务程序,都处于当前进程的上下文中,并没有发生进程上下文切换。
当系统调用或中断处理程序返回时,CPU 要从内核模式切换回用户模式,此时会执行操作系统的调用程序。如果发现就需队列中有比当前进程更高的优先级的进程,则会发生进程切换:当前进程信息被保存,切换到就绪队列中的那个高优先级进程;否则,直接返回当前进程的用户模式,不会发生上下文切换。
system call
System calls in most Unix-like systems are processed in kernel mode, which is accomplished by changing the processor execution mode to a more privileged one, but no process context switch is necessary
context switch
Some operating systems(Not include Linux) also require a context switch to move between user mode and kernel mode tasks. The process of context switching can have a negative impact on system performance
通过 vmstat 查看 context switch
一般 vmstat 工具的使用是通过两个数字参数来完成的,第一个参数是采样的时间间隔数,单位是秒,第二个参数是采样的次数,如:
root@local:~# vmstat 2 1
procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu----
r b swpd free buff cache si so bi bo in cs us sy id wa
1 0 0 3498472 315836 3819540 0 0 0 1 2 0 0 0 100 0
context switch 高,导致的争用其它案例
有很多种情况都会导致 context switch。MySQL 中的 mutex 和 RWlock 在获取不成功后,短暂 spin,还不成功,就会发生 context switch,sleep,等待唤醒。
在 MySQL 中,mutex 和 RWlock 导致的 context switch,一般在 show global status,show engine innodb mutex,show engine innodb status,performance_schema 等中会体现出来,针对不同的 mutex 和 RWlock 等待,可以采取不同的优化措施。
除了 MySQL 的 mutex 和 RWlock,还发现一种情况,是 MySQL 外的 mutex 竞争导致 context switch 高。
典型症状:
MySQL running 高,但系统 qps、tps 低
系统 context switch 很高,每秒超过 200K
在 MySQL 内存查不到 mutex 和 RWlock 竞争信息
SYS CPU 高,USER CPU 低
并发执行的 SQL 中出现 timestamp 字段,MySQL 的 time_zone 设置为 system
分析
--
对于使用 timestamp 的场景,MySQL 在访问 timestamp 字段时会做时区转换,当 time_zone 设置为 system 时,MySQL 访问每一行的 timestamp 字段时,都会通过 libc 的时区函数,获取 Linux 设置的时区,在这个函数中会持有 mutex,当大量并发 SQL 需要访问 timestamp 字段时,会出现 mutex 竞争。
MySQL 访问每一行都会做这个时区转换,转换完后释放 mutex,所有等待这个 mutex 的线程全部唤醒,结果又会只有一个线程会成功持有 mutex,其余又会再次 sleep,这样就会导致 context switch 非常高但 qps 很低,系统吞吐量急剧下降。
解决办法:设置 time_zone=’+8:00’,这样就不会访问 Linux 系统时区,直接转换,避免了 mutex 问题。
评论