你的位置:首页 > 数据库

[数据库]MySQL 调优基础(一) CPU和进程


一般而言,MySQL 的调优可以分为两个层面,一个是在MySQL层面上进行的调优,比如SQL改写,索引的添加,MySQL各种参数的配置;另一个层面是从操作系统的层面和硬件的层面来进行调优。操作系统的层面的调优,一般要先定位到是那种资源出现瓶颈——CPU、 内存、硬盘、网络,然后入手调优。所以其实MySQL 的调优,其实不是那么简单,它要求我们对 硬件、OS、MySQL 三者都具有比较深入的理解。比如 NUMA 架构的CPU是如何分配CPU的,以及是如何分配内存的,如何避免导致SWAP的发生;Linux 系统的IO调度算法选择哪一种——CFQ、DEADLINE、NOOP、还是 ANTICIPATORY;MySQL的redo log和undo log它们的写哪个是顺序写,哪个是随机写?

所以其实,如果想要对 MySQL 进行调优,要求我们必须对 硬件和OS,以及MySQL的内部实现原理,都要有很好的掌握。本文关于MySQL调优基础之 CPU和进程。

1. CPU 架构之 NUMA和SMP

SMP:称为共享内存访问CPU(Shared Memory Mulpti Processors), 也称为对称型CPU架构(Symmetry Multi Processors)

NUMA:非统一内存访问 (Non Uniform Memory Access)

它们最重要的区别在于内存是否绑定在各个物理CPU上,以及CPU如何访问内存:

SMP架构的CPU内部没有绑定内存,所有的CPU争用一个总线来访问所有共享的内存,优点是资源共享,而缺点是总线争用激烈。随着PC服务 器上的CPU数量变多(不仅仅是CPU核数),总线争用的弊端慢慢越来越明显,于是Intel在Nehalem CPU上推出了NUMA架构,而AMD也推出了基于相同架构的Opteron CPU。

NUMA 最大的特点是引入了node和distance的概念,node内部有多个CPU,以及绑定的内存。每个node的CPU和内存一般是相等。distance这个概念是用来定义各个node之间调用资源的开销。NUMA架构中内存和CPU的关系如下图所示:

 (node内部有多个CPU,node内部有内存;每两个node之间有 inter-connect)

NUMA架构的提出是为了适应多CPU,可以看到node内部的CPU对内存的访问分成了两种情况:

1)对node内部的内存的访问,一般称为 local access,显然访问速度是最快的;

2)对其它node中的内存的访问,一般称为 remote access,因为要通过 inter-connect,所以访问速度会慢一些;

因为CPU和内存是绑定而形成一个node,那么就涉及到CPU如何分配,内存如何分配的问题:

1)NUMA的CPU分配策略有cpunodebind、physcpubind。cpunodebind规定进程运行在某几个node之上,而physcpubind可以更加精细地规定运行在哪些核上。
2)NUMA的内存分配策略有localalloc、preferred、membind、interleave。localalloc规定进程从当前node上请求分配内存;而preferred比较宽松地指定了一个推荐的node来获取内存,如果被推荐的node上没有足够内存,进程可以尝试别的node。membind可以指定若干个node,进程只能从这些指定的node上请求分配内存。interleave规定进程从所有node上以RR算法交织地请求分配内存,达到随机均匀的从各个node中分配内存的目的。

NUMA 架构导致的问题——SWAP

因为NUMA架构的CPU,默认采用的是localalloc的内存分配策略,运行在本node内部CPU上的进程,会从本node内部的内存上分配内存,如果内存不足,则会导致swap的产生,严重影响性能!它不会向其它node申请内存。这是他最大的问题。

所以为了避免SWAP的产生,一定要将NUMA架构CPU的内存分配策略设置为:interleave; 这样设置的话,任何进程的内存分配,都会随机向各个node申请,虽然remote access会导致一点点的性能损失,但是他杜绝了SWAP导致的严重的性能损失。所以 interleave 其实是将NUMA架构的各个node中的内存,又重新虚拟成了一个共享的内存,但是和SMP不同的是,因为每两个node之间有 inter-connect ,所以又避免了SMP架构总线争用的缺陷

2. CPU 和 Linux 进程

进程应该是Linux中最重要的一个概念。进程运行在CPU上,是所有硬件资源分配的对象。Linux中用一个task_struct的结构来描述进程,描述了进程的各种信息、属性、资源。

Linux中进程的生命周期和它们涉及的调用

1)父进程调用fork() 产生一个新的自进程;

2)子进程调用exec() 指定自己要执行的代码;

3)子进程调用exit() 退出,进入zombie状态;

4)父进程调用wait(),等待子进程的返回,回收其所有资源;

Thread:

是一个执行单元,同一进程中所有线程,共享进程的资源。线程一般称为 LWP(Light Weight Process)轻量级进程。所以期限线程没有那么神秘,我们可以将其当做特殊的进程来看待。

进程的优先级和nice

进程的调度,涉及到进程的优先级。优先级使用nice level来表示,其值范围:19 ~ -20。值越小,优先级越大,默认为0.

一般如果我们想降低某个线程被调度的频率,就可以调高它的nice值(越nice,就越不会去争用CPU)。

进程的 context switch:

进程上下文切换,是一个极为重要的概念,因为他对性能影响极大。进程的调度,级涉及到进程上下文的切换。上下文切换,是指将进程的所有的信息,从CPU的register中flush到内存中,然后换上其它进程的上下文。频繁的上下文切换,将极大的影响性能。

CPU中断处理

CPU的中断处理是优先级最高的任务之一。中断分为:hard interrupte 和 soft interrupt.

因为中断发生,就会去运行中断处理程序,也就导致了context switch,所以过多的中断也会导致性能的下降。

进程的各种状态 state:

进程的各种状态,一定要搞清楚他们的含义,不然后面进程的 load average(top命令和uptime命令)等各种信息会看不懂。

1)TASK_RUNNING: In this state, a process is running on a CPU or waiting to run in the queue (run queue).

2)TASK_STOPPED: A process suspended by certain signals (for example SIGINT, SIGSTOP) is in this state. The process is waiting to be

     resumed by a signal such as SIGCONT.

3)TASK_INTERRUPTIBLE: In this state, the process is suspended and waits for a certain condition to be satisfied. If a process is in

     TASK_INTERRUPTIBLE state and it receives a signal to stop, the process state is changed and operation will be interrupted. A typical

     example of a TASK_INTERRUPTIBLE process is a process waiting for keyboard interrupt.

4)TASK_UNINTERRUPTIBLE: Similar to TASK_INTERRUPTIBLE. While a process in TASK_INTERRUPTIBLE state can be interrupted, 

     sending a signal does nothing to the process in TASK_UNINTERRUPTIBLE state. A typical example of a TASK_UNINTERRUPTIBLE

     process is a process waiting for disk I/O operation.

5)TASK_ZOMBIE: After a process exits with exit() system call, its parent should know of the termination. In TASK_ZOMBIE state, a

     process is waiting for its parent to be notified to release all the data structure.

除了 TASK_RUNNING 中的进程可能在运行之外,其它状态的进程都没有在运行。但是其实 TASK_UNINTERRUPTIBLE 比较特殊,它其实可以看做是在运行的,因为他是在等待磁盘操作完成,所以其实从系统的角度,而不是从进程的角度而言,其实系统是在为进程运行的。这也就是为什么 load average 中的数值,是包括了 TASK_RUNNING 和 TASK_UNINTERRUPTIBLE 两种状态的进程的平均值。

进程如何使用内存

进程的运行,必须申请和使用内存,最重要的包括堆和栈:

进程使用的内存,可以用 pmap, ps 等待命令行来查看。在后面会有站么的内存调优文章介绍。

CPU 调度:

前面介绍了CPU的调度涉及到进程的优先级和nice level. Linux中进程的调度算法是 O(1)的,所以进程数量的多少不会影响进程调度的效率。

进程的调度涉及到两个优先级数组(优先级队列):active, expired 。CPU按照优先级调度 active 队列中的进程,队列中所有进程调度完成之后,交换active队列和expired队列,继续调度。

NUMA架构的CPU在调度时,一般不会垮node节点进行调度,除非一个node节点CPU超载了并且请求进行负载均衡。因为垮node节点CPU调度影响性能。

3. Linux 如何度量 CPU

1)CPU utilization:最直观最重要的就是CPU的使用率。如果长期超过80%,则表明CPU遇到了瓶颈;

2)User time: 用户进程使用的CPU;该数值越高越好,表明越多的CPU用在了用户实际的工作上

3)System time: 内核使用的CPU,包括了硬中断、软中断使用的CPU;该数值越低越好,太高表明在网络和驱动层遇到瓶颈;

4)Waiting: CPU花在等待IO操作上的时间;该数值很高,表明IO子系统遇到瓶颈;

5)Idel time: CPU空闲的时间;

6)Load average: the average of the sum of TASK_RUNNING and TASK_UNINTERRUPTIBLE processes. If processes that request CPU time are blocked (which means that the CPU has no time to process them), the load average will increase. On the other hand, if each
process gets immediate access to CPU time and there are no CPU cycles lost, the load will decrease.

7)Context Switch: 上下文切换;

如何检测CPU:

检测CPU使用情况最好的工具是 top 命令:

3.1 top 命令 查看CPU 信息

要调优,那么就必须读懂 很多 命令行工具的输出。上面的top命令包括了很多的信息:

第一行:

top - 14:35:55 up 25 min, 4 users,   load average: 0.10, 0.07, 0.14

分别表示的是:当前系统时间;up 25 min表示已经系统已经运行25分钟; 4 users:表示系统中有4个登录用户;

load average: 分别表示最近 1 分钟, 5 分钟, 15分钟 CPU的负载的平均值。

(load average: the average of the sum of TASK_RUNNING and TASK_UNINTERRUPTIBLE processes);

这一行最重要的就是 load average

第二行:

Tasks: 92 total,  1 running, 91 sleeping , 0 stopped, 0 zombie

分别表示系统中的进程数:总共92个进程, 1个在运行,91个在sleep,0个stopped, 0个僵尸;

第三行:

Cpu(s): 0.0%us,  1.7 %sy, 0.0%ni, 97.7%id, 0.0%wa, 0.3%hi, 0.3%si, 0.0%st

这一行提供了关于CPU使用率的最重要的信息,分别表示 users time, system time, nice time, idle time, wait time, hard interrupte time, soft interrupted time, steal time; 其中最终要的是:users time, system time, wait time ,idle time 等等。nice time 表示用于调准进程nice level所花的时间。

第四行:

Mem:  total, used ,free, buffers

提供的是关于内存的信息,因为Linux会尽量的来使用内存来进行缓存,所以这些信息没有多大的价值,free数值小,并不代表存在内存瓶颈;

第五行:

Swap: total, used, free ,cached

提供的是关于swap分区的使用情况,这些信息也没有太大的价值,因为Linux的使用内存的机制决定的。used值很大并不代表存在内存瓶颈;

剩下是关于每个进程使用的资源的情况,进程的各种信息,按照使用CPU的多少排序,每个字段的含义如下:

PID: 表示进程ID;USER: 表示运行进程的用户;PR:表示进程优先级;NI:表示进程nice值,默认为0;

VIRT:The  total  amount  of  virtual  memory  used by the task.  It includes all code, data and shared libraries plus pages that have been swapped out. 进程占用的虚拟内存大小,包括了swap out的内存page;

RES: Resident size (kb)。The non-swapped physical memory a task is using. 进程使用的常驻内存大小,没有包括swap out的内存;

SHR:Shared Mem size (kb)。The amount of shared memory used by a task.  It simply reflects memory that could be potentially shared

         with other processes. 其实应该就是使用 shmget() 系统调用分配的共享内存,可以在多个进程之间共享访问。

S: 表示进程处于哪种状态:R: Running; S: sleeping; T: stoped; D: interrupted; Z:zomobie;

%CPU: 进程占用的CPU;

%MEM:进程占用的内存;

%TIME+: 进程运行时间;

COMMAND: 进程运行命令;

读懂 top 等相关命令行的信息是进行调优的基础。其实这些命令行的输出的含义,在man top中都有是否详细的说明。只要耐心看手册就行了。

上面的 top 命令默认是以 进程为单位来显示的,我们也可以以线程为单位来显示: top -H

可以看到以线程为单位是,Tasks totol数量明显增加了,有92增加到了 134,因为 mysqld 是线程架构的,后台有多个后台线程。

如果仅仅想查看 CPU 的 load average,使用uptime命令就行了:

[root@localhost ~]# uptime 15:26:59 up 1:15, 4 users, load average: 0.00, 0.02, 0.00

3.2 vmstat 查看CPU 信息

上面各个字段的含义:

FIELD DESCRIPTION FOR VM MODE  Procs    r: The number of processes waiting for run time.    b: The number of processes in uninterruptible sleep.  Memory    swpd: the amount of virtual memory used.    free: the amount of idle memory.    buff: the amount of memory used as buffers.    cache: the amount of memory used as cache.    inact: the amount of inactive memory. (-a option)    active: the amount of active memory. (-a option)  Swap    si: Amount of memory swapped in from disk (/s).    so: Amount of memory swapped to disk (/s).  IO    bi: Blocks received from a block device (blocks/s).    bo: Blocks sent to a block device (blocks/s).  System    in: The number of interrupts per second, including the clock.    cs: The number of context switches per second.  CPU    These are percentages of total CPU time.    us: Time spent running non-kernel code. (user time, including nice time)    sy: Time spent running kernel code. (system time)    id: Time spent idle. Prior to Linux 2.5.41, this includes IO-wait time.    wa: Time spent waiting for IO. Prior to Linux 2.5.41, included in idle.    st: Time stolen from a virtual machine. Prior to Linux 2.6.11, unknown.

和CPU相关的信息有 user time, system time, idle time, wait time, steal time;

另外提供了关于中断和上下文切换的信息。System 中的in,表示每秒的中断数,cs表示每秒的上下文切换数;

其它的字段是关于内存和磁盘的。

3.3 iostat 命令查看 CPU 信息

[root@localhost ~]# iostatLinux 2.6.32-504.el6.i686 (localhost.localdomain)    09/30/2015   _i686_ (1 CPU)avg-cpu: %user  %nice %system %iowait %steal  %idle      0.27  0.02  4.74  0.28  0.00  94.69Device:      tps  Blk_read/s  Blk_wrtn/s  Blk_read  Blk_wrtnscd0       0.01     0.08     0.00    536     0sda        1.64    42.27     8.97   290966   61720
[root@localhost ~]# iostat -c
Linux 2.6.32-504.el6.i686 (localhost.localdomain)       09/30/2015      _i686_  (1 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           0.26    0.02    4.72    0.27    0.00   94.72

提供了cpu的平均使用率。

3.4 ps 命令查看某个进程和某个线程的 CPU 信息

比如我想查看 mysqld 的相关信息:

[root@localhost ~]# ps -elf|grep mysql4 S root   1818 1719 0 80  0 - 1266 -   14:35 pts/1  00:00:00 /bin/sh /usr/local/mysql/bin/mysqld_safe --user=mysql4 S mysql   2029 1818 0 80  0 - 188524 -   14:36 pts/1  00:00:12 /usr/local/mysql/bin/mysqld --basedir=/usr/local/mysql --datadir=/var/lib/mysql --plugin-dir=/usr/local/mysql/lib/plugin --user=mysql --log-error=/var/log/mysqld.log --pid-file=/var/mysql/mysqld.pid --socket=/var/lib/mysql/mysql.sock4 S root   2041 1757 0 80  0 - 2605 -   14:36 pts/3  00:00:02 mysql -uroot -p0 S root   2806 1738 0 80  0 - 1088 -   15:54 pts/2  00:00:00 grep mysql[root@localhost ~]#[root@localhost ~]# ps -mp 2029 -o THREAD,pmem,rss,vszUSER   %CPU PRI SCNT WCHAN USER SYSTEM %MEM  RSS  VSZmysql   0.2  -  - -     -   - 43.1 444364 754096mysql   0.1 19  - -     -   -  -   -   -mysql   0.0 19  - -     -   -  -   -   -mysql   0.0 19  - -     -   -  -   -   -mysql   0.0 19  - -     -   -  -   -   -mysql   0.0 19  - -     -   -  -   -   -mysql   0.0 19  - -     -   -  -   -   -mysql   0.0 19  - -     -   -  -   -   -mysql   0.0 19  - -     -   -  -   -   -mysql   0.0 19  - -     -   -  -   -   -mysql   0.0 19  - -     -   -  -   -   -mysql   0.0 19  - -     -   -  -   -   -mysql   0.0 19  - -     -   -  -   -   -mysql   0.0 19  - -     -   -  -   -   -mysql   0.0 19  - -     -   -  -   -   -mysql   0.0 19  - -     -   -  -   -   -mysql   0.0 19  - -     -   -  -   -   -mysql   0.0 19  - -     -   -  -   -   -mysql   0.0 19  - -     -   -  -   -   -mysql   0.0 19  - -     -   -  -   -   -mysql   0.0 19  - -     -   -  -   -   -mysql   0.0 19  - -     -   -  -   -   -mysql   0.0 19  - -     -   -  -   -   -

先获得 mysqld 的基础PID 2029,

然后查看其线程的信息:ps -mp 2047 -o THREAD,pmem,rss,vsz ( -p 指定进程PID, -o 指定输出格式,参见man ps)

第一列就是关于线程CPU的信息。

关于ps我们一般使用 ps -elf , 如果想查看线程,可以 ps -elLf,其中的L表示 Leight weight process轻量级进程。

3.5 mpstat(multi processor stat) 命令查看多核CPU中每个CPU核心的信息

[root@localhost ~]# mpstatLinux 2.6.32-504.el6.i686 (localhost.localdomain)    09/30/2015   _i686_ (1 CPU)04:11:50 PM CPU  %usr  %nice  %sys %iowait  %irq  %soft %steal %guest  %idle04:11:50 PM all  0.26  0.02  4.30  0.27  0.26  0.15  0.00  0.00  94.74

这里因为虚拟机中的单核CPU,所以只显示all,没有显示其它核心的CPU使用情况。

4. CPU 相关调优

1)使用上面介绍的工具:top, vmstat, iostat, ps -mp xxx -o, mpstat 等,可以确认是否存在 CPU 瓶颈。然后确认 user time, system time, wait time, context switch......那种占用比例高,确认是哪个进程占用CPU高。如果能确认是 mysqld 的相关进程,那么就可以从 mysql 上入手进行调优。比如使用mysql的命令 show processlist ;查看是哪个sql导致的,找到sql之后,进行优化或者重写等等,或者将其放到slave上去运行。

2)如果是 非必须的进程占用CPU,那么可以杀掉,然后使用cron让其在非高峰期去执行;或者使用 renice 命令降低其优先级;

[root@localhost ~]# renice -n -10 -p 20412041: old priority 10, new priority -10

将进程 2041 的优先级设置为 -10.

3)可以跟踪进程,查找原因:strace -aef -p spid -o file

[root@localhost ~]# strace -aef -p 2041 -o mysql.txtProcess 2041 attached - interrupt to quit^CProcess 2041 detached[root@localhost ~]# ll mysql.txt-rw-r--r-- 1 root root 10091 Sep 30 16:44 mysql.txt[root@localhost ~]# head mysql.txtread(0, "\33", 1) = 1read(0, "[", 1) = 1read(0, "A", 1) = 1write(1, "select version();", 17) = 17read(0, "\n", 1) = 1write(1, "\n", 1) = 1ioctl(0, SNDCTL_TMR_STOP or TCSETSW, {B38400 opost isig icanon echo ...}) = 0rt_sigprocmask(SIG_BLOCK, [HUP INT QUIT TERM CONT TSTP WINCH], [], 8) = 0rt_sigaction(SIGINT, {0x8053ac0, [INT], SA_RESTART}, NULL, 8) = 0rt_sigaction(SIGTSTP, {SIG_DFL, [], 0}, NULL, 8) = 0

4)换更好的CPU。