FAQ系列 | SLAVE为什么一直不动了

导读

遇到SLAVE延迟很大,binlog apply position一直不动的情况如何排查?

问题描述

收到SLAVE延迟时间一直很大的报警,于是检查一下SLAVE状态(无关状态我给隐去了):

          Slave_IO_State: Waiting for master to send event
         Master_Log_File: mysql-bin.000605
     Read_Master_Log_Pos: 1194
          Relay_Log_File: mysql-relay-bin.003224
           Relay_Log_Pos: 295105
   Relay_Master_Log_File: mysql-bin.000604
        Slave_IO_Running: Yes
       Slave_SQL_Running: Yes
              Last_Errno: 0
              Last_Error: 
     Exec_Master_Log_Pos: 294959
         Relay_Log_Space: 4139172581
   Seconds_Behind_Master: 10905

可以看到,延迟确实很大,而且从多次show slave status的结果来看,发现binlog的position一直不动。

     Read_Master_Log_Pos: 1194
          Relay_Log_File: mysql-relay-bin.003224
           Relay_Log_Pos: 295105
   Relay_Master_Log_File: mysql-bin.000604
     Exec_Master_Log_Pos: 294959
         Relay_Log_Space: 4139172581

从processlist的中也看不出来有什么不对劲的SQL在跑:

******************** 1. row ******************
     Id: 16273070
   User: system user
   Host:
     db: NULL
Command: Connect
   Time: 4828912
  State: Waiting for master to send event
   Info: NULL
********************* 2. row *****************
     Id: 16273071
   User: system user
   Host:
     db: NULL
Command: Connect
   Time: 9798
  State: Reading event from the relay log
   Info: NULL

在master上查看相应binlog,确认都在干神马事:

[yejr@imysql.com]# mysqlbinlog -vvv --base64-output=decode-rows -j 294959 mysql-bin.000604 | more

/*!40019 SET @@session.max_insert_delayed_threads=0*/;
/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/;
DELIMITER /*!*/;
**# at 294959**
#160204  6:16:30 server id 1  end_log_pos 295029     **Query    thread_id=461151**    **exec_time=2144**    error_code=0
SET TIMESTAMP=1454537790/*!*/;
SET @@session.pseudo_thread_id=461151/*!*/;
SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=0, @@session.unique_checks=1, @@session.autocommit=1/*!*/;
SET @@session.sql_mode=0/*!*/;
SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/;
/*!\C latin1 *//*!*/;
SET @@session.character_set_client=8,@@session.collation_connection=8,@@session.collation_server=33/*!*/;
SET @@session.lc_time_names=0/*!*/;
SET @@session.collation_database=DEFAULT/*!*/;
BEGIN
/*!*/;
# at 295029
# at 295085
# at 296040
# at 297047
# at 298056
# at 299068
# at 300104

上面这段内容的几个关键信息:

# at 294959   — binlog起点
thread_id=461151    — master上执行的线程ID
exec_time=2144    — 该事务执行总耗时

再往下看都是一堆的binlog position信息,通过这种方式可读性不强,我们换一种姿势看看:

[yejr@imysql.com (test)]> show binlog events in 'mysql-bin.000604' from 294959 limit 10;
+------------------+--------+-------------+-----------+-------------+----------------------------+
| Log_name         | Pos    | Event_type  | Server_id | End_log_pos | Info                       |
+------------------+--------+-------------+-----------+-------------+----------------------------+
| mysql-bin.000604 | 294959 | Query       |         1 |      295029 | BEGIN                      |
| mysql-bin.000604 | 295029 | Table_map   |         1 |      295085 | table_id: 84 (bacula.File) |
| mysql-bin.000604 | 295085 | Delete_rows |         1 |      296040 | table_id: 84               |
| mysql-bin.000604 | 296040 | Delete_rows |         1 |      297047 | table_id: 84               |
| mysql-bin.000604 | 297047 | Delete_rows |         1 |      298056 | table_id: 84               |
| mysql-bin.000604 | 298056 | Delete_rows |         1 |      299068 | table_id: 84               |
| mysql-bin.000604 | 299068 | Delete_rows |         1 |      300104 | table_id: 84               |
| mysql-bin.000604 | 300104 | Delete_rows |         1 |      301116 | table_id: 84               |
| mysql-bin.000604 | 301116 | Delete_rows |         1 |      302147 | table_id: 84               |
| mysql-bin.000604 | 302147 | Delete_rows |         1 |      303138 | table_id: 84               |

+—————————+————+——————-+—————-+——————-+——————————————+

可以看到,这个事务不干别的,一直在删除数据。
这是一个Bacula备份系统,会每天自动删除一个月前的过期数据。
事实上,这个事务确实非常大,从binlog的294959开始,一直到这个binlog结束4139169218,一直都是在干这事,总共大概有3.85G的binlog要等着apply。

-rw-rw---- 1 mysql mysql 1.1G Feb  3 03:07 mysql-bin.000597
-rw-rw---- 1 mysql mysql 1.1G Feb  3 03:19 mysql-bin.000598
-rw-rw---- 1 mysql mysql 2.1G Feb  3 03:33 mysql-bin.000599
-rw-rw---- 1 mysql mysql 1.4G Feb  3 03:45 mysql-bin.000600
-rw-rw---- 1 mysql mysql 1.8G Feb  3 04:15 mysql-bin.000601
-rw-rw---- 1 mysql mysql 1.3G Feb  3 04:53 mysql-bin.000602
-rw-rw---- 1 mysql mysql 4.5G Feb  4 06:16 mysql-bin.000603
-rw-rw---- 1 mysql mysql 3.9G Feb  4 06:52 mysql-bin.000604
-rw-rw---- 1 mysql mysql 1.2K Feb  4 06:52 mysql-bin.000605

可以看到上面的历史binlog,个别情况下,一个事务里一次性要删除数据量太大了,导致binlog文件远超预设的1G,最大的达到4.5G之多。

怎么解决

由于这是Bacula备份系统内置生成的大事务,除非去修改它的源码,否则没有太好的办法。

对于我们一般的应用而言,最好是攒够一定操作后,就先提交一下事务,比如删除几千条记录后提交一次,而不是像本例这样,一个删除事务消耗了将近3.9G的binlog日质量,这种就非常可怕了。

除了会导致SLAVE看起来一直不动以外,还可能会导致某些数据行(data rows)被长时间锁定不释放,而导致大量行锁等待发生。

 

关于MySQL的方方面面大家想了解什么,可以直接留言回复,我会从中选择一些热门话题进行分享。 同时希望大家多多转发,多一些阅读量是老叶继续努力分享的绝佳助力,谢谢大家 :)

最后打个广告,运维圈人士专属铁观音茶叶微店上线了,访问:http://yejinrong.com 获得专属优惠

优化系列 | 实例解析MySQL性能瓶颈排查定位

导读

从一个现场说起,全程解析如何定位性能瓶颈。

排查过程

收到线上某业务后端的MySQL实例负载比较高的告警信息,于是登入服务器检查确认。

1. 首先我们进行OS层面的检查确认

登入服务器后,我们的目的是首先要确认当前到底是哪些进程引起的负载高,以及这些进程卡在什么地方,瓶颈是什么。

通常来说,服务器上最容易成为瓶颈的是磁盘I/O子系统,因为它的读写速度通常是最慢的。即便是现在的PCIe SSD,其随机I/O读写速度也是不如内存来得快。当然了,引起磁盘I/O慢得原因也有多种,需要确认哪种引起的。

第一步,我们一般先看整体负载如何,负载高的话,肯定所有的进程跑起来都慢。
可以执行指令 w 或者 sar -q 1 来查看负载数据,例如:

[yejr@imysql.com:~ ]# w
 11:52:58 up 702 days, 56 min,  1 user,  load average: 7.20, 6.70, 6.47
USER     TTY      FROM              LOGIN@   IDLE   JCPU   PCPU WHAT
root     pts/0    1.xx.xx.xx        11:51    0.00s  0.03s  0.00s w

或者 sar -q 的观察结果:

[yejr@imysql.com:~ ]# sar -q 1
Linux 2.6.32-431.el6.x86_64 (yejr.imysql.com)     01/13/2016     _x86_64_    (24 CPU)
02:51:18 PM   runq-sz  plist-sz   ldavg-1   ldavg-5  ldavg-15   blocked
02:51:19 PM         4      2305      6.41      6.98      7.12         3
02:51:20 PM         2      2301      6.41      6.98      7.12         4
02:51:21 PM         0      2300      6.41      6.98      7.12         5
02:51:22 PM         6      2301      6.41      6.98      7.12         8
02:51:23 PM         2      2290      6.41      6.98      7.12         8

load average大意表示当前CPU中有多少任务在排队等待,等待越多说明负载越高,跑数据库的服务器上,一般load值超过5的话,已经算是比较高的了。

引起load高的原因也可能有多种:

  1. 某些进程/服务消耗更多CPU资源(服务响应更多请求或存在某些应用瓶颈);
  2. 发生比较严重的swap(可用物理内存不足);
  3. 发生比较严重的中断(因为SSD或网络的原因发生中断);
  4. 磁盘I/O比较慢(会导致CPU一直等待磁盘I/O请求);

这时我们可以执行下面的命令来判断到底瓶颈在哪个子系统:

[yejr@imysql.com:~ ]# top
top - 11:53:04 up 702 days, 56 min,  1 user,  load average: 7.18, 6.70, 6.47
Tasks: 576 total,   1 running, 575 sleeping,   0 stopped,   0 zombie
Cpu(s):  7.7%us,  3.4%sy,  0.0%ni, 77.6%id, 11.0%wa,  0.0%hi,  0.3%si,  0.0%st
Mem:  49374024k total, 32018844k used, 17355180k free,   115416k buffers
Swap: 16777208k total,   117612k used, 16659596k free,  5689020k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
14165 mysql     20   0 8822m 3.1g 4672 S 162.3  6.6  89839:59 mysqld
40610 mysql     20   0 25.6g  14g 8336 S 121.7 31.5 282809:08 mysqld
49023 mysql     20   0 16.9g 5.1g 4772 S  4.6 10.8   34940:09 mysqld

很明显是前面两个mysqld进程导致整体负载较高。
而且,从 Cpu(s) 这行的统计结果也能看的出来,%us%wa 的值较高,表示当前比较大的瓶颈可能是在用户进程消耗的CPU以及磁盘I/O等待上
我们先分析下磁盘I/O的情况。

执行 sar -d 确认磁盘I/O是否真的较大:

[yejr@imysql.com:~ ]# sar -d 1
Linux 2.6.32-431.el6.x86_64 (yejr.imysql.com)     01/13/2016     _x86_64_    (24 CPU)
11:54:32 AM    dev8-0   5338.00 162784.00   1394.00     30.76      5.24      0.98      0.19    100.00
11:54:33 AM    dev8-0   5134.00 148032.00  32365.00     35.14      6.93      1.34      0.19    100.10
11:54:34 AM    dev8-0   5233.00 161376.00    996.00     31.03      9.77      1.88      0.19    100.00
11:54:35 AM    dev8-0   4566.00 139232.00   1166.00     30.75      5.37      1.18      0.22    100.00
11:54:36 AM    dev8-0   4665.00 145920.00    630.00     31.41      5.94      1.27      0.21    100.00
11:54:37 AM    dev8-0   4994.00 156544.00    546.00     31.46      7.07      1.42      0.20    100.00

再利用 iotop 确认到底哪些进程消耗的磁盘I/O资源最多:

[yejr@imysql.com:~ ]# iotop
Total DISK READ: 60.38 M/s | Total DISK WRITE: 640.34 K/s
  TID  PRIO  USER     DISK READ  DISK WRITE  SWAPIN     IO>    COMMAND
16397 be/4 mysql       8.92 M/s    0.00 B/s  0.00 % 94.77 % mysqld --basedir=/usr/local/m~og_3320/mysql.sock --port=3320
 7295 be/4 mysql      10.98 M/s    0.00 B/s  0.00 % 93.59 % mysqld --basedir=/usr/local/m~og_3320/mysql.sock --port=3320
14295 be/4 mysql      10.50 M/s    0.00 B/s  0.00 % 93.57 % mysqld --basedir=/usr/local/m~og_3320/mysql.sock --port=3320
14288 be/4 mysql      14.30 M/s    0.00 B/s  0.00 % 91.86 % mysqld --basedir=/usr/local/m~og_3320/mysql.sock --port=3320
14292 be/4 mysql      14.37 M/s    0.00 B/s  0.00 % 91.23 % mysqld --basedir=/usr/local/m~og_3320/mysql.sock --port=3320

可以看到,端口号是3320的实例消耗的磁盘I/O资源比较多,那就看看这个实例里都有什么查询在跑吧。

2. MySQL层面检查确认

首先看下当前都有哪些查询在运行:

[yejr@imysql.com(db)]> mysqladmin pr|grep -v Sleep
+----+----+----------+----+-------+-----+--------------+-----------------------------------------------------------------------------------------------+
| Id |User| Host     | db |Command|Time | State        | Info                                                                                          |
+----+----+----------+----+-------+-----+--------------+-----------------------------------------------------------------------------------------------+
| 25 | x | 10.x:8519 | db | Query | 68  | Sending data | select max(Fvideoid) from (select Fvideoid from t where Fvideoid>404612 order by Fvideoid) t1 |
| 26 | x | 10.x:8520 | db | Query | 65  | Sending data | select max(Fvideoid) from (select Fvideoid from t where Fvideoid>484915 order by Fvideoid) t1 |
| 28 | x | 10.x:8522 | db | Query | 130 | Sending data | select max(Fvideoid) from (select Fvideoid from t where Fvideoid>404641 order by Fvideoid) t1 |
| 27 | x | 10.x:8521 | db | Query | 167 | Sending data | select max(Fvideoid) from (select Fvideoid from t where Fvideoid>324157 order by Fvideoid) t1 |
| 36 | x | 10.x:8727 | db | Query | 174 | Sending data | select max(Fvideoid) from (select Fvideoid from t where Fvideoid>324346 order by Fvideoid) t1 |
+----+----+----------+----+-------+-----+--------------+-----------------------------------------------------------------------------------------------+

可以看到有不少慢查询还未完成,从slow query log中也能发现,这类SQL发生的频率很高。
这是一个非常低效的SQL写法,导致需要对整个主键进行扫描,但实际上只需要取得一个最大值而已,从slow query log中可看到:

Rows_sent: 1  Rows_examined: 5502460

每次都要扫描500多万行数据,却只为读取一个最大值,效率非常低。

经过分析,这个SQL稍做简单改造即可在个位数毫秒级内完成,原先则是需要150-180秒才能完成,提升了N次方。
改造的方法是:对查询结果做一次倒序排序,取得第一条记录即可。而原先的做法是对结果正序排序,取最后一条记录,汗啊。。。

写在最后,小结

在这个例子中,产生瓶颈的原因比较好定位,SQL优化也不难,实际线上环境中,通常有以下几种常见的原因导致负载较高:

  1. 一次请求读写的数据量太大,导致磁盘I/O读写值较大,例如一个SQL里要读取或更新几万行数据甚至更多,这种最好是想办法减少一次读写的数据量;
  2. SQL查询中没有适当的索引可以用来完成条件过滤、排序(ORDER BY)、分组(GROUP BY)、数据聚合(MIN/MAX/COUNT/AVG等),添加索引或者进行SQL改写吧;
  3. 瞬间突发有大量请求,这种一般只要能扛过峰值就好,保险起见还是要适当提高服务器的配置,万一峰值抗不过去就可能发生雪崩效应;
  4. 因为某些定时任务引起的负载升高,比如做数据统计分析和备份,这种对CPU、内存、磁盘I/O消耗都很大,最好放在独立的slave服务器上执行;
  5. 服务器自身的节能策略发现负载较低时会让CPU降频,当发现负载升高时再自动升频,但通常不是那么及时,结果导致CPU性能不足,抗不过突发的请求;
  6. 使用raid卡的时候,通常配备BBU(cache模块的备用电池),早期一般采用锂电池技术,需要定期充放电(DELL服务器90天一次,IBM是30天),我们可以通过监控在下一次充放电的时间前在业务低谷时提前对其进行放电,不过新一代服务器大多采用电容式电池,也就不存在这个问题了。
  7. 文件系统采用ext4甚至ext3,而不是xfs,在高I/O压力时,很可能导致%util已经跑到100%了,但iops却无法再提升,换成xfs一般可获得大幅提升;
  8. 内核的io scheduler策略采用cfq而非deadline或noop,可以在线直接调整,也可获得大幅提升。

 

关于MySQL的方方面面大家想了解什么,可以直接留言回复,我会从中选择一些热门话题进行分享。 同时希望大家多多转发,多一些阅读量是老叶继续努力分享的绝佳助力,谢谢大家 :)

最后打个广告,运维圈人士专属铁观音茶叶微店上线了,访问:http://yejinrong.com 获得专属优惠

FAQ系列 | B+树索引和哈希索引的区别

导读

在MySQL里常用的索引数据结构有B+树索引和哈希索引两种,我们来看下这两种索引数据结构的区别及其不同的应用建议。

二者区别

备注:先说下,在MySQL文档里,实际上是把B+树索引写成了BTREE,例如像下面这样的写法:

CREATE TABLE t(
aid int unsigned not null auto_increment,
userid int unsigned not null default 0,
username varchar(20) not null default ‘’,
detail varchar(255) not null default ‘’,
primary key(aid),
unique key(uid) USING BTREE,
key (username(12)) USING BTREE此处 uname 列只创建了最左12个字符长度的部分索引
)engine=InnoDB;

一个经典的B+树索引数据结构见下图:
20160106B树索引
(图片源自网络)

B+树是一个平衡的多叉树,从根节点到每个叶子节点的高度差值不超过1,而且同层级的节点间有指针相互链接。

在B+树上的常规检索,从根节点到叶子节点的搜索效率基本相当,不会出现大幅波动,而且基于索引的顺序扫描时,也可以利用双向指针快速左右移动,效率非常高。

因此,B+树索引被广泛应用于数据库、文件系统等场景。顺便说一下,xfs文件系统比ext3/ext4效率高很多的原因之一就是,它的文件及目录索引结构全部采用B+树索引,而ext3/ext4的文件目录结构则采用Linked list, hashed B-tree、Extents/Bitmap等索引数据结构,因此在高I/O压力下,其IOPS能力不如xfs。

详细可参见:

https://en.wikipedia.org/wiki/Ext4
https://en.wikipedia.org/wiki/XFS

哈希索引的示意图则是这样的:
20160106哈希索引
(图片源自网络)

简单地说,哈希索引就是采用一定的哈希算法,把键值换算成新的哈希值,检索时不需要类似B+树那样从根节点到叶子节点逐级查找,只需一次哈希算法即可立刻定位到相应的位置,速度非常快。

从上面的图来看,B+树索引和哈希索引的明显区别是:

  • 如果是等值查询,那么哈希索引明显有绝对优势,因为只需要经过一次算法即可找到相应的键值;当然了,这个前提是,键值都是唯一的。如果键值不是唯一的,就需要先找到该键所在位置,然后再根据链表往后扫描,直到找到相应的数据;
  • 从示意图中也能看到,如果是范围查询检索,这时候哈希索引就毫无用武之地了,因为原先是有序的键值,经过哈希算法后,有可能变成不连续的了,就没办法再利用索引完成范围查询检索;
  • 同理,哈希索引也没办法利用索引完成排序,以及like ‘xxx%’ 这样的部分模糊查询(这种部分模糊查询,其实本质上也是范围查询);
  • 哈希索引也不支持多列联合索引的最左匹配规则
  • B+树索引的关键字检索效率比较平均,不像B树那样波动幅度大,在有大量重复键值情况下,哈希索引的效率也是极低的,因为存在所谓的哈希碰撞问题

后记

在MySQL中,只有HEAP/MEMORY引擎表才能显式支持哈希索引(NDB也支持,但这个不常用),InnoDB引擎的自适应哈希索引(adaptive hash index)不在此列,因为这不是创建索引时可指定的。

还需要注意到:HEAP/MEMORY引擎表在mysql实例重启后,数据会丢失。

通常,B+树索引结构适用于绝大多数场景,像下面这种场景用哈希索引才更有优势:

在HEAP表中,如果存储的数据重复度很低(也就是说基数很大),对该列数据以等值查询为主,没有范围查询、没有排序的时候,特别适合采用哈希索引

例如这种SQL:
SELECT … FROM t WHERE C1 = ?; — 仅等值查询

在大多数场景下,都会有范围查询、排序、分组等查询特征,用B+树索引就可以了。

 

关于MySQL的方方面面大家想了解什么,可以直接留言回复,我会从中选择一些热门话题进行分享。 同时希望大家多多转发,多一些阅读量是老叶继续努力分享的绝佳助力,谢谢大家 :)

最后打个广告,运维圈人士专属铁观音茶叶微店上线了,访问:http://yejinrong.com 获得专属优惠

FAQ系列 | 监控平均SQL响应时长

导读

MySQL里如何监控平均SQL响应时长?

问题由来

对MySQL的性能指标监控,除了关注tps(每秒可执行的事务数)、qps(每秒请求数)两个衡量吞吐量的重要指标外,还应该监控平均SQL响应时长指标。

怎么做

有几个可选方案:

1、利用MySQL提供的benchmark()函数。这个函数的作用是模拟进行N次某种调用,这样一来,我们就可以利用这个函数调用N次专门的存储过程,根据其执行耗时,以此作为平均SQL响应时长的依据;

2、利用pt-query-digest工具,并结合tcpdump实时抓取每个SQL请求,也就能分析出每个SQL请求的响应时长了;

3、使用Percona或者MariaDB分支版本提供的QUERY_RESPONSE_TIME插件功能,它可以帮我们统计平均SQL响应时长的分布区间,类似直方图功能;

第一种相对比较简单但不够精确(不过也是够用的),第二种略麻烦些但可以看到每次请求的详细记录,第三种则只能看到整体的分布,无法看到每次请求的详细记录。

写在最后

监控性能指标时,除了关注吞吐量,还应该关注每次请求的平均响应时长。以高速公路收费站为例,有几个收费口基本可表示其并发收费能力(tps),而每辆车的平均通行时间如果很久的话,相信你也是受不了的是不是 :)

 

关于MySQL的方方面面大家想了解什么,可以直接留言回复,我会从中选择一些热门话题进行分享。 同时希望大家多多转发,多一些阅读量是老叶继续努力分享的绝佳助力,谢谢大家 :)

最后打个广告,运维圈人士专属铁观音茶叶微店上线了,访问:http://yejinrong.com 获得专属优惠

FAQ系列 | lower_case_table_names迷思

导读

关于 lower_case_table_names 选项的设置的建议是怎样的呢?

问题由来

我个人认为,纠结于这个选项设置源于有些项目是从ORACLE或SQL Server迁移过来,在这两个数据库系统中,都无需关心数据表的大小写。而在MySQL中,默认是要区分大小写的(因为Unix/Linux文件系统是区分文件名大小写的),除非在windows系统下(windows系统是不区分大小写的)。

老叶的建议

我在公司制定的规范是要求默认设置 lower_case_table_names=0 的,也就是区分大小写。那么问题来了,如果是从ORACLE或SQL Server迁移到MYSQL的应用应该怎么处理呢?
我的建议是:

  • 首先,检查确认在应用程序中(或者抓取一段时间的请求日志),数据表名的写法是大写、小写还是混用,如果都是大写或者都是小写,那就更简单些了;
  • 其次,根据上面检查的结果,确定迁移到MySQL后统一使用大写还是小写(使用哪种规则的改动代价更小);
  • 最后,利用Linux下的awk\sed等工具,将包含数据表关键字的地方全部替换成第二部定义好的表名方案;

这样一来,就可以完成数据表名方案的切换了。

当然了,肯定有人(比如某领导、某PM,你懂得的,O(∩_∩)O哈哈~)会说全部修改表名风险太大,需要全面测试,这个项目时间进度很紧张,希望能先上线。这种情况就没办法了,只能闲设置 lower_case_table_names=1,然后迁移数据,优先保证项目进度。

but,即便这时候,我们也建议数据表初始化时,统一采用大写或小写的表名,在项目的后续过程中,通过开启general log的方式,把所有请求SQL中使用的表名都记录下来,然后检查还有哪些和我们定义的规则不一样,再逐渐完善修改,最终达到最终目标。

写在最后

强烈建议在定义数据库设计规范时,统一采用全部都大写或全部都小写的数据表命名规则,没必要为了所谓的美观,弄出一堆大小写混合的表名,是在太操蛋了。

关于MySQL的方方面面大家想了解什么,可以直接留言回复,我会从中选择一些热门话题进行分享。 同时希望大家多多转发,多一些阅读量是老叶继续努力分享的绝佳助力,谢谢大家 :)

关于MySQL的方方面面大家想了解什么,可以直接留言回复,我会从中选择一些热门话题进行分享。 同时希望大家多多转发,多一些阅读量是老叶继续努力分享的绝佳助力,谢谢大家 :)

最后打个广告,运维圈人士专属铁观音茶叶微店上线了,访问:http://yuhongli.com 获得专属优惠

 

MySQLer马拉松跑团招募成员啦

说正事前,先容我先扯扯。

关于晨跑

大概2个月前,我调整了下跑步时间。以前是下班后约上同事去公司附近的公园跑步,之后再回去吃饭。现在则改成了早晨6点起来,独自一人在附近的公园跑步。相对于前者,后者的好处多多:

  1. 通过早起反过来督促早睡,搞IT的都习惯晚睡,这点非常不好。晨跑结束后,洗个热水澡,一整天精神都会很好;
  2. 跑步时间更充裕,6点起来,9点半上班,中间有大段的时间,现在我一般都持续跑10公里约1个小时多一点;
  3. 由于晨跑的时间比较充裕,跑步过程中可以安心思考一些事情,不像夜跑,可能要非常专注看路什么的,今天的这篇短文就是晨跑中构思起来的;

关于晨跑的建议:一开始可能无法早起,可以先定个闹钟,强迫自己坚持早起1周,1周后估计就基本上能适应了,然后就可以开始愉快健康的晨跑之旅了。

关于跑步烧钱这事

在以前,单纯的以为跑步很省钱,没想到越来越烧钱。。。
从2012年开始跑到现在,大概败了这些东西:

  1. 买了3双鞋,约1K
  2. 运动手表,约1.7k
  3. 小腿套、髌骨贴、腰包、紧身衣,约0.5k
  4. 数次外出参赛,合计约3k

加起来也不是小数目了,看来以后少外出参赛,自己平时多跑跑就好了,哈哈。

重要的事:MySQLer马拉松跑团招募

想要搞这个跑团的出发点是鼓励各位MySQLer多运动、多健身、防猝死,同时也和CMUG组织结合起来,希望以后有机会拉到商业赞助,为跑团成员外出参赛提供一定额度的赞助,嘿嘿。

怎么参加MySQLer马拉松跑团

当然了,我们也欢迎各位运维圈同行都来加入,不光是MySQLer。
可以加入QQ群 112718255,或者扫描下面的二维码加入

或者,也可以参加我们的悦跑圈跑团“17173约跑团”,ID号是:15377(一个账号只能加入一个跑团),加入请备注姓名、所在城市或所在公司。我们跑团本周已经跃居福建本省排名第4(本月本省排名12),还在持续上升中,嘿!

关于其他

  1. 趁年轻多运动,不管是跑步、游泳还是其他,省得老了以后给下一代添麻烦;
  2. 并且培养一个业余爱好,不管是运动,还是摄影、书画、下棋等等,以后退休了可以有事做,不至于一下子闲下来不适应。不过,搞IT的,除了财富自由了提前退休的,有几个人能捱到自然退休年龄呢,哈哈;
  3. 每个年龄段都有一些该做的事,比如中学时好好读书,大学时除了好好读书外,还应该多参加社会实践,更应该好好谈场纯粹的恋爱,30岁左右结婚生娃(现在该再加上一条,35岁左右生二娃,哈哈哈),做个普通人,挺好的,嗯。

更多阅读:我为什么要参加马拉松

利用event为zabbix数据表定期添加和删除分区

导读

利用MySQL的event来自动维护表分区。

我们去年就开始把zabbix数据库改成用TokuDB来支撑,并且启用了表分区(详情见:迁移Zabbix数据库到TokuDB)。这样做的好处很明显,较早的历史数据可以通过删除分区快速废弃掉。要知道,zabbix数据表默认是没有针对时间字段创建索引的,因此如果执行删除的SQL命令,其效率会很差,而直接删除分区就快多了。

先看history表的分区规则:

CREATE TABLE history (
 itemid bigint(20) unsigned NOT NULL,
 clock int(11) NOT NULL DEFAULT '0',
 value double(16,4) NOT NULL DEFAULT '0.0000',
 ns int(11) NOT NULL DEFAULT '0',
 KEY history_1 (itemid,clock)
 ) ENGINE=TokuDB DEFAULT CHARSET=utf8 ROW_FORMAT=TOKUDB_QUICKLZ
 PARTITION BY RANGE (clock)
 (PARTITION p20150531 VALUES LESS THAN (1433088000) ENGINE = TokuDB,
 ...
 PARTITION p20160411 VALUES LESS THAN (1460390400) ENGINE = TokuDB);

对这个表,我们每天要的是:创建一个新的分区,而后删除N个月前的历史旧分区。这个工作可以通过系统的cron来实施,也可以利用MySQL自身的event来做,在这里我们选择用event,没什么特殊的原因,只是想顺便尝试下event而已,呵呵。

一个定期调度的event写起来并不难,下面是参考样例,相信很快就能看明白:

delimiter $$$
 drop event if exists zabbix_alter_partition_daily;
 CREATE EVENT zabbix_alter_partition_daily
 ON SCHEDULE EVERY 1 DAY -- 每天执行
 DO
 begin

 -- 记日志
 insert into zlogs select 0, now(), date_format(date_sub(now(), INTERVAL 180 DAY),
 " ALTER TABLE history DROP PARTITION p%Y%m%d");

 -- 删除history表180天前的旧分区
 -- 用PREPARE & EXECUTE 准备和执行删除的SQL
 SET @drop_p_stmt = date_format(date_sub(now(), INTERVAL 180 DAY)," ALTER TABLE history DROP PARTITION p%Y%m%d");
 PREPARE drop_p_stmt FROM @drop_p_stmt;
 EXECUTE drop_p_stmt;

 -- 创建history表30天后的新分区
 insert into zlogs select 0, now(), concat(
 date_format(date_add(now(), INTERVAL 180 DAY)," ALTER TABLE history ADD PARTITION ( PARTITION p%Y%m%d VALUES LESS THAN "),
 "(", 
 unix_timestamp( date_add(date_format(now(), "%Y%m%d"), INTERVAL 31 DAY) ),
 "))");

 SET @add_p_stmt = concat(
 date_format(date_add(now(), INTERVAL 30 DAY)," ALTER TABLE history ADD PARTITION ( PARTITION p%Y%m%d VALUES LESS THAN "),
 "(",
 unix_timestamp( date_add(date_format(now(), "%Y%m%d"), INTERVAL 31 DAY) ),
 "))");

 PREPARE add_p_stmt FROM @add_p_stmt;
 EXECUTE add_p_stmt;

 end $$$
 delimiter ;

FAQ系列 | 如何保证主从复制数据一致性

导读

MySQL主从复制环境中,如何才能保证主从数据的一致性呢?

关于主从复制

现在常用的MySQL高可用方案,十有八九是基于 MySQL的主从复制(replication)来设计的,包括常规的一主一从、双主模式,或者半同步复制(semi-sync replication)。

我们常常把MySQL replication说成是MySQL同步(sync),但事实上这个过程是异步(async)的。大概过程是这样的:

  1. 在master上提交事务后,并且写入binlog,返回事务成功标记;
  2. 将binlog发送到slave,转储成relay log;
  3. 在slave上再将relay log读取出来应用。

步骤1和步骤3之间是异步进行的,无需等待确认各自的状态,所以说MySQL replication是异步的。

MySQL semi-sync replication在之前的基础上做了加强完善,整个流程变成了下面这样:

  1. 首先,master和至少一个slave都要启用semi-sync replication模式;
  2. 某个slave连接到master时,会主动告知当前自己是否处于semi-sync模式;
  3. 在master上提交事务后,写入binlog后,还需要通知至少一个slave收到该事务,等待写入relay log并成功刷新到磁盘后,向master发送“slave节点已完成该事务”确认通知;
  4. master收到上述通知后,才可以真正完成该事务提交,返回事务成功标记;
  5. 在上述步骤中,当slave向master发送通知时间超过rpl_semi_sync_master_timeout设定值时,主从关系会从semi-sync模式自动调整成为传统的异步复制模式。

半同步复制看起来很美好有木有,但如果网络质量不高,是不是出现抖动,触发上述第5条的情况,会从半同步复制降级为普通复制;此外,采用半同步复制,会导致master上的tps性能下降非常严重,最严重的情况下可能会损失50%以上。

这样来看,除非需要非常严格保证数据一致性等迫不得已的场景,就不太建议使用半同步复制了。当然了,事实上我们也可以通过加强程序端的逻辑控制,来避免主从数据不一致时发生逻辑错误,比如说如果在从上读取到的数据和主不一致的话,那么就触发主从间的一次数据修复工作。或者,我们也可以用 pt-table-checksum & pt-table-sync 两个工具来校验并修复数据,只要运行频率适当,是可行的。

真想要提高多节点间的数据一致性,可以考虑采用PXC方案。现在已知用PXC规模较大的有qunar、sohu,如果团队里初期没有人能比较专注PXC的话,还是要谨慎些,毕竟和传统的主从复制差异很大,出现问题时需要花费更多精力去排查解决。

如何保证主从复制数据一致性

上面说完了异步复制、半同步复制、PXC,我们回到主题:在常规的主从复制场景里,如何能保证主从数据的一致性,不要出现数据丢失等问题呢?

在MySQL中,一次事务提交后,需要写undo、写redo、写binlog,写数据文件等等。在这个过程中,可能在某个步骤发生crash,就有可能导致主从数据的不一致。为了避免这种情况,我们需要调整主从上面相关选项配置,确保即便发生crash了,也不能发生主从复制的数据丢失。

1. 在master上修改配置

innodb_flush_log_at_trx_commit = 1
sync_binlog = 1

上述两个选项的作用是:保证每次事务提交后,都能实时刷新到磁盘中,尤其是确保每次事务对应的binlog都能及时刷新到磁盘中,只要有了binlog,InnoDB就有办法做数据恢复,不至于导致主从复制的数据丢失。

2. 在slave上修改配置

master_info_repository = "TABLE"
relay_log_info_repository = "TABLE"
relay_log_recovery = 1

上述前两个选项的作用是:确保在slave上和复制相关的元数据表也采用InnoDB引擎,受到InnoDB事务安全的保护,而后一个选项的作用是开启relay log自动修复机制,发生crash时,会自动判断哪些relay log需要重新从master上抓取回来再次应用,以此避免部分数据丢失的可能性。

通过上面几个选项的调整,就可以确保主从复制数据不会发生丢失了。但是,这并不能保证主从数据的绝对一致性,因为,有可能设置了ignore\do\rewrite等replication规则,或者某些SQL本身存在不确定因素,或者人为在slave上修改数据,最终导致主从数据不一致。这种情况下,可以采用pt-table-checksumpt-table-sync 工具来进行数据的校验和修复。

FAQ系列 | 提问的正确姿势

导读

在群里向老叶提问的正确姿势是怎样的?

我的QQ好友已超过1500人,其中至少有一半是曾经有过技术交流,或者找我帮助解决MySQL相关问题的。大致平均下来,每天约要帮忙解答2-3次问题。

对我来说,这已然成了不小的额外“工作量”。因此,并不是所有的问题我都能回复,有些是能力不够,有些则是没兴趣回答,大概只有不到1/3的问题我会正面回复。

先说下哪些是我没能力回答的吧,大概有下面这样的:

  1. 想和我交流MySQL源码的,不好意思,我就一运维DBA,源码方面我并不擅长,倒是可以找阿里云RDS团队或其他同行聊聊;
  2. 直接问我“这个SQL执行很慢,怎么优化呢”,也不好意思,只有这种一句话的问题描述,我确实没能力回答。真想获得帮助的话,可以参考下方的“提问的正确姿势”;
  3. “你好,我想实现xxx功能,请问SQL该怎么写呢”,更不好意思了,写SQL还真不是我的强项,非要勉为其难让我写的话,麻烦先给我发个红包,谢谢。

有哪些是我没兴趣回答的呢?

  1. 发来一条消息,问“在吗”。这是被我一直诟病的最糟糕的打招呼方式,平白无故浪费别人时间。心情好的时候,我或许会回一个“你猜/hi”,不好的时候,直接关掉当没看到;
  2. 跟我说“老叶,能帮忙推荐一个牛一点的DBA吗,谢谢”。嗯,当然可以推荐,但麻烦您把大概要求说一下总可以吧,如果不是熟人,我的做法是直接关闭对话框;
  3. “请问MySQL里xxx命令是什么用途呢”,抱歉,我不是人肉查询机,麻烦自己看手册。

那么,提问的正确姿势是什么呢?
先举几个例子吧:
Q: 这个SQL为什么很慢?
A: 这是个什么SQL呢?
Q: 这是zabbix后台的一个SQL。
……尼妹,贴一下原始SQL以及执行计划能死啊?

还是以这个SQL效率问题为例,我期望得到的回复是这样的:

  1. 相关表DDL长什么样,执行 SHOW CREATE TABLE 即可查看;
  2. 这个SQL的执行计划是怎样的,用 EXPLAIN 解析下就行了(包含完整的SQL);
  3. 必要的话,还可以提供SQL执行时的PROFILE结果;

另一种情况,如果是性能上存在问题的话,建议提供下面几个信息:

  1. (负载较高时)在服务器上执行top后的截屏;
  2. (负载较高时)运行vmstat 1 50后的截屏;
  3. (负载较高时)提供PROCESSLIST根绝耗时排序后的截屏;
  4. 提供服务器硬件配置信息,MySQL配置文件;
    上述这些信息可以打成一个压缩包发出来。

当有了上面这3个信息,相信技术群里有很多人都可以帮到你了。

 

关于MySQL的方方面面大家想了解什么,可以直接留言回复,我会从中选择一些热门话题进行分享。 同时希望大家多多转发,多一些阅读量是老叶继续努力分享的绝佳助力,谢谢大家 :)

最后打个广告,运维圈人士专属铁观音茶叶微店上线了,访问:http://yuhongli.com 获得专属优惠

 

FAQ系列 | MySQL索引之主键索引

导读

在MySQL里,主键索引和辅助索引分别是什么意思,有什么区别?

上次的分享我们介绍了聚集索引和非聚集索引的区别,本次我们继续介绍主键索引和辅助索引的区别。

1、主键索引

主键索引,简称主键,原文是PRIMARY KEY,由一个或多个列组成,用于唯一性标识数据表中的某一条记录。一个表可以没有主键,但最多只能有一个主键,并且主键值不能包含NULL。

在MySQL中,InnoDB数据表的主键设计我们通常遵循几个原则:

  1. 采用一个没有业务用途的自增属性列作为主键;
  2. 主键字段值总是不更新,只有新增或者删除两种操作;
  3. 不选择会动态更新的类型,比如当前时间戳等。

这么做的好处有几点:

  1. 新增数据时,由于主键值是顺序增长的,innodb page发生分裂的概率降低了;可以参考以往的分享“[MySQL FAQ]系列 — 为什么InnoDB表要建议用自增列做主键”;
  2. 业务数据有变更时,不修改主键值,物理存储位置发生变化的概率降低了,innodb page中产生碎片的概率也降低了。

MyISAM表因为是堆组织表,主键类型设计方面就可以不用这么讲究了。

2、辅助索引

辅助索引,就是我们常规所指的索引,原文是SECONDARY KEY。辅助索引里还可以再分为唯一索引非唯一索引

唯一索引其实应该叫做唯一性约束,它的作用是避免一列或多列值存在重复,是一种约束性索引。

3、主键索引和辅助索引的区别

在MyISAM引擎中,唯一索引除了key值允许存在NULL外,其余的和主键索引没有本质性区别。也就是说,在MyISAM引擎中,不允许存在NULL值的唯一索引,本质上和主键索引是一回事

而在InnoDB引擎中,主键索引和辅助索引的区别就很大了。主键索引会被选中作为聚集索引,而唯一索引和普通辅助索引间除了唯一性约束外,在存储上没本质区别

从查询性能上来说,在MyISAM表中主键索引和不允许有NULL的唯一索引的查询性能是相当的在InnoDB表通过唯一索引查询则需要多一次从辅助索引到主键索引的转换过程InnoDB表基于普通索引的查找代价更高,因为每次检索到结果后,还需要至少再多检索一次才能确认是否还有更多符合条件的结果,主键索引和唯一索引就不需要这么做了。

经过测试,对100万行数据的MyISAM做随机检索(整数类型),主键和唯一索引的效率基本一样,普通索引的检索效率则慢了30%以上。换成InnoDB表的话,唯一索引比主键索引效率约慢9%,普通索引比主键索引约慢了50%以上。

 

关于MySQL的方方面面大家想了解什么,可以直接留言回复,我会从中选择一些热门话题进行分享。 同时希望大家多多转发,多一些阅读量是老叶继续努力分享的绝佳助力,谢谢大家 :)

最后打个广告,运维圈人士专属铁观音茶叶微店上线了,访问:http://yuhongli.com 获得专属优惠