月度归档:2015年07月

[MySQL FAQ]系列 — 怎么计算打开文件数

有时候,我们会遇到类似下面的报错信息:

.....
[ERROR] /usr/local/mysql/bin/mysqld: Can't open file: './yejr/access.frm' (errno: 24)
[ERROR] /usr/local/mysql/bin/mysqld: Can't open file: './yejr/accesslog.frm' (errno: 24)
......
[ERROR] Error in accept: Too many open files
....

提示信息很明显,打开文件数达到上限了,需要提高上限,或者释放部分已打开的表文件描述符

 

在MySQL中,有几个地方会存在文件描述符限制:

1、在Server层,整个mysqld实例打开文件总数超过用户进程级的文件数限制,需要检查内核 fs.file-max 限制、进程级限制 ulimit -n 及MySQL中的 open-files-limit 选项,是否有某一个超限了。任何一个条件超限了,就会抛出错误。

2、虽然Server层总文件数没有超,但InnoDB层也有限制,所有InnoDB相关文件打开总数不能超过 innodb-open-files 选项限制。否则的话,会先把最早打开的InnoDB文件描述符关闭,才能打开新的文件,但不会抛出错误,只有告警信息。

相应地,如果提示超出限制,则可以使用下面方法提高上限:

1、首先,提高内核级总的限制。执行:sysctl -w fs.file-max=3264018;

2、其次,提高内核对用户进程级的打开文件数限制。执行:ulimit -n 204800;

3、最后,适当提高MySQL层的几个参数:open-files-limit、innodb-open-files、table-open-cache、table-definition-cache

关于前面两个限制网上可以找到很多详细解释,我就不多说了,重点来说下MySQL相关的4个选项。

1、open-files-limit
它限制了mysqld进程可持有的最大打开文件数,相当于是一个小区的总电闸,一旦超限,小区里所有住户都得停电。
5.6.7(含)以前,默认值0,最大和OS内核限制有关;
5.6.8(含)以后,默认值会自动计算,最大和OS内核限制有关。

在5.6.8及以后,其自动计算的几个限制规则见下,哪个计算结果最大就以哪个为上限:

1) 10 + max_connections + (table_open_cache * 2)
2) max_connections * 5
3) open_files_limit value specified at startup, 5000 if none

 

2、innodb-open-files

限制InnoDB引擎中表空间文件最大打开的数量,相当于自己家中电箱里的某个电路保险,该电路短路的话,会自动跳闸,而不会影响其他电路,去掉短路源后重新按上去就可以使用。

其值最低20,默认400,只计算了包含ibdata*、ib_logfile*、*.ibd 等三类文件,redo log不计算在内,5.6以后可独立undo log,我还未进行测试,应该也不会被计算在内,有兴趣的朋友可验证下。

 

3、table-definition-cache

该cache用于缓存 .frm 文件,该选项也预示着 .frm 文件同时可打开最大数量。
5.6.7 以前默认值400;
5.6.7 之后是自动计算的,且最低为400,自动计算公式:400 + (table-open-cache / 2)。

对InnoDB而言,该选项只是软性限制,如果超过限制了,则会根据LRU原则,把旧的条目删除,加入新的条目。

此外,innodb-open-files 也控制着最大可打开的表数量,和 table-definition-cache 都起到限制作用,以其中较大的为准。如果没配置限制,则通常选择 table-definition-cache 作为上限,因为它的默认值是 200,比较大。

 

4、table-open-cache

该cache用于缓存各种所有数据表文件描述符。
5.6.7 以前,默认值400,范围:1 – 524288;
5.6.8 – 5.6.11,默认值2000,范围:1 – 524288;
5.6.12以后,默认值2000(且能自动计算),范围:1 – 524288。

 

补充说明1:关于如何计算表文件描述符的建议:

table-open-cache 通常和 max-connections 有关系,建议设置为 max_connections * N,N的值为平均每个查询中可能总共会用到的表数量,同时也要兼顾可能会产生临时表。

 

补充说明2:MySQL会在下列几种情况把表从table cache中删掉:

1、table cache已满,并且正要打开一个新表时;
2、table cache中的条目数超过 table_open_cache 设定值,并且有某些表已经长时间未访问了;

3、执行刷新表操作时,例如执行 FLUSH TABLES,或者 mysqladmin flush-tables 或 mysqladmin refresh

 

补充说明3:MySQL采用下述方法来分配table cache:

1、当前没在用的表会被释放掉,从最近最少使用的表开始;

2、当要打开一个新表,当前的cache也满了且无法释放任何一个表时,table cache会临时加大,临时加大的table cache中的表不用了之后,会被立刻释放掉。

 

延伸阅读:
1、[MySQL FAQ]系列 — Too many open files
2、[MySQL FAQ]系列 — mysql如何计算打开文件数
3、8.4.3.1 How MySQL Opens and Closes Tables

 

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

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

 

MySQL手册chm格式文档

较早前官方还提供chm格式的文档,现在已经不再提供了,搞不清楚什么原因。

我手头上还有5.5英文版和5.1中文版(国内团队翻译的,质量略差,不过也可以作为参考)chm格式的手册,已经放在百度云盘上,点此下载

同时招募志愿者将官方5.6版本手册整理成chm格式,或者如果您已经整理好了,不妨共享给我一份,感谢!

老叶倡议:MySQL压力测试基准值

通常,我们会出于以下几个目的对MySQL进行压力测试:

1、确认新的MySQL版本性能相比之前差异多大,比如从5.6变成5.7,或者从官方版本改成Percona分支版本;
2、确认新的服务器性能是否更高,能高多少,比如CPU升级了、阵列卡cache加大了、从机械盘换成SSD盘了;
3、确认一些新的参数调整后,对性能影响多少,比如 innodb_flush_log_at_trx_commit、sync_binlog 等参数;
4、确认即将上线的新业务对MySQL负载影响多少,是否能承载得住,是否需要对服务器进行扩容或升级配置;

针对上面这几种压测的目的,相应的测试方法也有所不同。

先说第四种,需要和线上业务结合起来,这时候就需要自行开发测试工具,或者利用 tcpcopy 将线上实际用户请求导向测试环境,进行仿真模拟测试。

对于前三种,我们通常采用基准测试就可以。比较常用的MySQL基准压力测试工具有 tpcc-mysqlsysbenchmysqlslap 等几个。

关于压力测试工具的使用,可以查看我之前在ORACLE技术嘉年华上的分享:MySQL压力测试经验,在这里不再细说。

基于促进同行间的交流,统一MySQL压测标准,并且可以相互分享、对比、借鉴测试结果的目的。因此老叶特别发起MySQL压力测试基准值倡议。建议大家采用以下几种压力测试基准值。

倡议:MySQL压力测试建议基准值(2015试行版)

倡议:MySQL压力测试建议基准值(2015试行版)

也可以查看本文附件excel文档:压力测试基准建议及数据采集模板,里面已附带了压力测试相关的数据采集点建议,压测结果整理及自动生成对比图表。欢迎各位同行拍砖提出不同的见解和补充意见,先谢过大家。

 

关于压力测试的其他几个方面:

1、如何避免压测时受到缓存的影响
【老叶建议】有2点建议
a、填充测试数据比物理内存还要大,至少超过 innodb_buffer_pool_size 值,不能将数据全部装载到内存中,除非你的本意就想测试全内存状态下的MySQL性能。
b、每轮测试完成后,都重启mysqld实例,并且用下面的方法删除系统cache,释放swap(如果用到了swap的话),甚至可以重启整个OS。

[root@imysql.com]# sync  -- 将脏数据刷新到磁盘
[root@imysql.com]# echo 3 > /proc/sys/vm/drop_caches  -- 清除OS Cache
[root@imysql.com]# swapoff -a && swapon -a

 

2、如何尽可能体现线上业务真实特点
【老叶建议】有2点建议
a、其实上面已经说过了,就是自行开发测试工具或者利用 tcpcopy(或类似交换机的mirror功能) 将线上实际用户请求导向测试环境,进行仿真模拟测试。
b、利用 http_loadsiege 工具模拟真实的用户请求URL进行压力测试,这方面我不是太专业,可以请教企业内部的压力测试同事。

 

3、压测结果如何解读
【老叶建议】压测结果除了tps/TpmC指标外,还应该关注压测期间的系统负载数据,尤其是 iops、iowait、svctm、%util、每秒I/O字节数(I/O吞吐)、事务响应时间(tpcc-mysql/sysbench 打印的测试记录中均有)。另外,如果I/O设备能提供设备级 IOPS、读写延时 数据的话,也应该一并关注。

假如两次测试的tps/TpmC结果一样的话,那么谁的 事务响应时间、iowait、svctm、%util、读写延时 更低,就表示那个测试模式有更高的性能提升空间。

 

4、如何加快tpcc_load加载数据的效率
【老叶建议】tpcc_load其实是可以并行加载的,一方面是可以区分 ITEMS、WAREHOUSE、CUSTOMER、ORDERS 四个维度的数据并行加载。
另外,比如最终想加载1000个 warehouse的话,也可以分开成1000个并发并行加载的。看下 tpcc_load 工具的参数就知道了:

usage: tpcc_load [server] [DB] [user] [pass] [warehouse]
OR
tpcc_load [server] [DB] [user] [pass] [warehouse] [part] [min_wh] [max_wh]
* [part]: 1=ITEMS 2=WAREHOUSE 3=CUSTOMER 4=ORDERS

本来想自己写个并行加载脚本的,后来发现万能的github上已经有人做好了,我就直接拿来用了,这是项目链接 tpcc_load_parallel.sh,加载效率至少提升10倍以上。

 

延伸阅读:

 

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

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

 

MySQL 5.7版本新特性连载(五)

本文是基于MySQL-5.7.7-rc版本,未来可能 还会发生更多变化。

1、支持多源复制(Multi-source replication),这对采用分库分表的同学绝对是个超级重磅福音。可以把多个MASTER的数据归并到一个实例上, 有助于提高SLAVE服务器的利用率。不过如果是同一个表的话,会存在主键和唯一索引冲突的风险,需要提前做好规划。

【新特性实践】
MySQL 5.7的多源复制采用多通道的模式,例如用以下方法可以创建多个复制通道,将多个MASTER上的数据复制到同一个SLAVE节点中去:

-- 需要先把 MASTER_INFO_REPOSITORY 和 RELAY_LOG_INFO_REPOSITORY 改成 TABLE 模式
[yejr@imysql.com]> SET GLOBAL MASTER_INFO_REPOSITORY = "TABLE";
Query OK, 0 rows affected (0.00 sec)

[yejr@imysql.com]> SET GLOBAL RELAY_LOG_INFO_REPOSITORY = "TABLE";
Query OK, 0 rows affected (0.00 sec)

-- 创建第一个复制通道
[yejr@imysql.com]> CHANGE MASTER TO MASTER_HOST='1.2.3.4', MASTER_USER='user', MASTER_PASSWORD='repl' FOR CHANNEL 'MASTER-01';
Query OK, 0 rows affected, 2 warnings (0.00 sec)

-- 创建第二个复制通道
[yejr@imysql.com]> CHANGE MASTER TO MASTER_HOST='2.3.4.5', MASTER_USER='user', MASTER_PASSWORD='repl' FOR CHANNEL 'MASTER-02';
Query OK, 0 rows affected, 2 warnings (0.00 sec)

-- 查看第二个复制通道的状态
[yejr@imysql.com]> SHOW SLAVE STATUS FOR CHANNEL 'MASTER-02';
*************************** 1. row ***************************
               Slave_IO_State: 
                  Master_Host: 2.3.4.5
                  Master_User: user
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: 
          Read_Master_Log_Pos: 4
               Relay_Log_File: yejr-relay-bin-master@002d02.000001
                Relay_Log_Pos: 4
        Relay_Master_Log_File: 
             Slave_IO_Running: No
            Slave_SQL_Running: No
              Replicate_Do_DB: 
          Replicate_Ignore_DB: 
           Replicate_Do_Table: 
       Replicate_Ignore_Table: 
      Replicate_Wild_Do_Table: 
  Replicate_Wild_Ignore_Table: 
                   Last_Errno: 0
                   Last_Error: 
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 0
              Relay_Log_Space: 154
              Until_Condition: None
               Until_Log_File: 
                Until_Log_Pos: 0
           Master_SSL_Allowed: No
           Master_SSL_CA_File: 
           Master_SSL_CA_Path: 
              Master_SSL_Cert: 
            Master_SSL_Cipher: 
               Master_SSL_Key: 
        Seconds_Behind_Master: NULL
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error: 
               Last_SQL_Errno: 0
               Last_SQL_Error: 
  Replicate_Ignore_Server_Ids: 
             Master_Server_Id: 0
                  Master_UUID: 
             Master_Info_File: mysql.slave_master_info
                    SQL_Delay: 0
          SQL_Remaining_Delay: NULL
      Slave_SQL_Running_State: 
           Master_Retry_Count: 86400
                  Master_Bind: 
      Last_IO_Error_Timestamp: 
     Last_SQL_Error_Timestamp: 
               Master_SSL_Crl: 
           Master_SSL_Crlpath: 
           Retrieved_Gtid_Set: 
            Executed_Gtid_Set: f0df162a-1a39-11e5-883a-782bcb65f419:1-11025782
                Auto_Position: 0
         Replicate_Rewrite_DB: 
                 Channel_Name: master-02
1 row in set (0.00 sec)

其他和复制相关的SQL指令和以往也基本一样,只需在加上 FOR CHANNEL ‘CHANNEL-NAME’ 子句即可。

此外,还支持在线修改replication filter规则,不过不是太建议使用filter规则,因此不重点介绍了。执行下面的SQL命令可以完成filter规则修改:

[yejr@imysql.com]> CHANGE REPLICATION FILTER
 REPLICATE_DO_DB = (d1), REPLICATE_IGNORE_DB = (d2);

 

2、支持多线程复制(Multi-Threaded Slaves, 简称MTS),在5.6版本中实现了SCHEMA级别的并行复制,不过意义不大,因为我们线上大部分实例的读写压力基本集中在某几个数据表,基本无助于缓解复制延迟问题。倒是MariaDB的多线程并行复制大放异彩,有不少人因为这个特性选择MariaDB(比如我也是其一,呵呵)。

MySQL 5.7 MTS支持两种模式,一种是和5.6一样,另一种则是基于binlog group commit实现的多线程复制,也就是MASTER上同时提交的binlog在SLAVE端也可以同时被apply,实现并行复制。关于MTS的更多详细介绍可以查看姜承尧的分享 MySQL 5.7 并行复制实现原理与调优,我这里就不重复说了。

值得一提的是,经过对比测试,5.7采用新的并行复制后,仍然会存在一定程度的延迟,只不过相比5.6版本减少了86%,相比MariaDB的并行复制延迟也小不少

 

下一期,我们继续讲讲5.7的其他新特性。

延伸阅读:

整理的比较仓促,若有遗漏或失误,请留言回复,谢谢!

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

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

 

老叶观点:MySQL开发规范之我见

大多数MySQL规范在网上也都能找得到相关的分享,在这里要分享的是老叶个人认为比较重要的,或者容易被忽视的,以及容易被混淆的一些地方。

1、默认使用InnoDB引擎
【老叶观点】已多次呼吁过了,InnoDB适用于几乎99%的MySQL应用场景,而且在MySQL 5.7的系统表都改成InnoDB了,还有什么理由再死守MyISAM呢。

此外,频繁读写的InnoDB表,一定要使用具有自增/顺序特征的整型作为显式主键

参考】:[MySQL FAQ]系列 — 为什么InnoDB表要建议用自增列做主键

 

2、字符集选择utf-8
【老叶观点】若为了节省磁盘空间,则建议选择latin1。建议选择utf-8通常是为了所谓的“通用性”,但事实上用户提交的utf-8数据也一样可以以latin1字符集存储

用latin1存储utf-8数据可能遇到的麻烦是,如果有基于中文的检索时,可能无法100%准确(老叶亲自简单测试常规的中文完检索全不是问题,也就是一般的中文对比是没问题的)。

用latin1字符集存储utf-8数据的做法是:在web端(用户端)的字符集是utf-8,后端程序也采用utf-8来处理,但 character_set_client、character_set_connection、character_set_results、character_set_database、character_set_server 这几个都是 latin1,且数据表、字段的字符集也是latin1。或者说数据表采用latin1,每次连接后执行 SET NAMES LATIN1 即可。

参考】:小谈MySQL字符集

 

3、InnoDB表行记录物理长度不超过8KB
【老叶观点】InnoDB的data page默认是16KB,基于B+Tree的特点,一个data page中需要至少存储2条记录。因此,当实际存储长度超过8KB(尤其是TEXT/BLOB列)的大列(large column)时会引起“page-overflow存储”,类似ORACLE中的“行迁移”。

因此,如果必须使用大列(尤其是TEXT/BLOB类型)且读写频繁的话,则最好把这些列拆分到子表中,不要和主表放在一起存储。如果不太频繁,可以考虑继续保留在主表中。

当然了,如果将 innodb_page_size 选项修改成 8KB,那么行记录物理长度建议不超过4KB。

参考】:[MySQL优化案例]系列 — 优化InnoDB表BLOB列的存储效率

 

4、是否使用分区表
【老叶观点】在一些使用分区表后明显可以提升性能或者运维便利性的场景下,还是建议使用分区表。

比如老叶就在zabbix的数据库采用TokuDB引擎的前提下,又根据时间维度使用了分区表。这样的好处是保证zabbix日常应用不受到影响前提下,方便管理员例行删除过去数据,只需要删除相应分区即可,不需再执行一个非常慢的DELETE而影响整体性能。

参考】:迁移Zabbix数据库到TokuDB

 

5、是否使用存储过程、触发器
【老叶观点】在一些合适的场景下,用存储过程、触发器也完全没问题。

我们以前就是利用存储完成游戏业务逻辑处理,性能上不是问题,而且一旦需求有变更,只需修改存储过程,变更代价很低。我们还利用触发器维护一个频繁更新的表,对这个表的所有变更都将部分字段同步更新到另一个表中(类似物化视图的变相实现),也不存在性能问题。

不要把MySQL的存储过程和触发器视为洪水猛兽,用好的话,没有问题的,真遇到问题了再优化也不迟。另外,MySQL因为没有物化视图,因此视图能不用就尽量少用吧。

 

6、选择合适的类型
【老叶观点】除了常见的建议外,还有其他几个要点:

6.1、用INT UNSIGNED存储IPV4地址,用INET_ATON()、INET_NTOA()进行转换,基本上没必要使用CHAR(15)来存储。

6.2、枚举类型可以使用ENUM,ENUM的内部存储机制是采用TINYINT或SMALLINT(并非CHAR/VARCHAR),性能一点都不差,记住千万别用CHAR/VARCHAR 来存储枚举数据。

6.3、还个早前一直在传播的“常识性误导”,建议用TIMESTAMP取代DATETIME。其实从5.6开始,建议优先选择DATETIME存储日期时间,因为它的可用范围比TIMESTAMP更大,物理存储上仅比TIMESTAMP多1个字节,整体性能上的损失并不大。

6.4、所有字段定义中,默认都加上NOT NULL约束,除非必须为NULL(但我也想不出来什么场景下必须要在数据库中存储NULL值,可以用0来表示)。在对该字段进行COUNT()统计时,统计结果更准确(值为NULL的不会被COUNT统计进去),或者执行 WHERE column IS NULL 检索时,也可以快速返回结果。

6.5、尽可能不要直接 SELECT * 读取全部字段,尤其是表中存在 TEXT/BLOB 大列的时候。可能本来不需要读取这些列,但因为偷懒写成 SELECT * 导致内存buffer pool被这些“垃圾”数据把真正需要缓冲起来的热点数据给洗出去了。

 

7、关于索引
【老叶观点】除了常见的建议外,还有几个要点:

7.1、超过20个长度的字符串列,最好创建前缀索引而非整列索引(例如:ALTER TABLE t1 ADD INDEX(user(20))),可以有效提高索引利用率,不过它的缺点是对这个列排序时用不到前缀索引。前缀索引的长度可以基于对该字段的统计得出,一般略大于平均长度一点就可以了。

7.2、定期用 pt-duplicate-key-checker 工具检查并删除重复的索引。比如 index idx1(a, b) 索引已经涵盖了 index idx2(a),就可以删除 idx2 索引了。

7.3、有多字段联合索引时,WHERE中过滤条件的字段顺序无需和索引一致,但如果有排序、分组则就必须一致了。

比如有联合索引 idx1(a, b, c),那么下面的SQL都可以完整用到索引

SELECT ... WHERE b = ? AND c = ? AND a = ?;  --注意到,WHERE中字段顺序并没有和索引字段顺序一致
SELECT ... WHERE b = ? AND a = ? AND c = ?;
SELECT ... WHERE a = ? AND b IN (?, ?) AND c = ?;
SELECT ... WHERE a = ? AND b = ? ORDER BY c;
SELECT ... WHERE a = ? AND b IN (?, ?) ORDER BY c;
SELECT ... WHERE a = ? ORDER BY b, c;
SELECT ... ORDER BY a, b, c;  -- 可利用联合索引完成排序

 

而下面几个SQL则只能用到部分索引,或者可利用到ICP特性

SELECT ... WHERE b = ? AND a = ?; -- 只能用到 (a, b) 部分
SELECT ... WHERE a IN (?, ?) AND b = ?; -- EXPLAIN显示只用到 (a, b) 部分索引,同时有ICP
SELECT ... WHERE (a BETWEEN ? AND ?) AND b = ?; -- EXPLAIN显示只用到 (a, b) 部分索引,同时有ICP
SELECT ... WHERE a = ? AND b IN (?, ?); -- EXPLAIN显示只用到 (a, b) 部分索引,同时有ICP
SELECT ... WHERE a = ? AND (b BETWEEN ? AND ?) AND c = ?; -- EXPLAIN显示用到 (a, b, c) 整个索引,同时有ICP
SELECT ... WHERE a = ? AND c = ?; -- EXPLAIN显示只用到 (a) 部分索引,同时有ICP
SELECT ... WHERE a = ? AND c >= ?; -- EXPLAIN显示只用到 (a) 部分索引,同时有ICP

ICP(index condition pushdown)是MySQL 5.6的新特性,其机制会让索引的其他部分也参与过滤,减少引擎层和server层之间的数据传输和回表请求,通常情况下可大幅提升查询效率。

 

下面的几个SQL完全用不到该索引

SELECT ... WHERE b = ?;
SELECT ... WHERE b = ? AND c = ?;
SELECT ... WHERE b = ? AND c = ?;
SELECT ... ORDER BY b;
SELECT ... ORDER BY b, a;

 

从上面的几个例子就能看的出来,以往强调的WHERE条件字段顺序要和索引顺序一致才能使用索引的 “常识性误导 无需严格遵守。

此外,有些时候查询优化器指定的索引或执行计划可能并不是最优的,可以手工指定最优索引,或者修改session级的 optimizer_switch 选项,关闭某些导致效果反而更差的特性(比如index merge通常是好事,但也遇到过用上index merge后反而更差的,这时候要么强制指定其中一个索引,要么可以临时关闭 index merge 特性)。

 

8、其他8
.1、哪怕是基于索引的条件过滤,如果优化器意识到总共需要扫描的数据量超过30%时(ORACLE里貌似是20%,MySQL目前是30%,没准以后会调整),就会直接改变执行计划为全表扫描,不再使用索引。

8.2、多表JOIN时,要把过滤性最大(不一定是数据量最小哦,而是只加了WHERE条件后过滤性最大的那个)的表选为驱动表。此外,如果JOIN之后有排序,排序字段一定要属于驱动表,才能利用驱动表上的索引完成排序。

8.3、绝大多数情况下,排序的代价通常要来的更高,因此如果看到执行计划中有 Using filesort,优先创建排序索引吧。

8.4、利用 pt-query-digest 定期分析slow query log,并结合 Box Anemometer 构建slow query log分析及优化系统。

参考】:[MySQL FAQ]系列 — EXPLAIN结果中哪些信息要引起关注

 

备注:若无特别说明,以上规范建议适用于MySQL 5.6及之前的版本(并且主要是5.6版本,尤其是ICP特性、DATETIME变化这两个地方)。5.7及之后的版本可能会有些变化,个别规范建议需要相应调整。

 

延伸阅读:

 

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

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

 

MySQL 5.7版本新特性连载(四)

本文是基于MySQL-5.7.7-rc版本,未来可能 还会发生更多变化。

1、SQL MODE变化
a. 默认启用 STRICT_TRANS_TABLES 模式
b. 对 ONLY_FULL_GROUP_BY 模式实现了更复杂的特性支持,并且也被默认启用;
c. 其他被默认启用的sql mode还有 NO_ENGINE_SUBSTITUTION;

【iMySQL建议】
对广大MySQL使用者而言,以往不是那么严格的模式还是很方便的,在5.7版本下可能会觉得略为不适,慢慢习惯吧。比如向一个20字符长度的VARCHAR列写入30个字符,在以前会自动截断并给个提示告警,而在5.7版本下,则直接抛出错误了。个人认为这倒是一个好的做法,避免各种奇葩的写法。

【新特性实践】

-- 查看默认的 sql_mode
[yejr@imysql.com]> select @@sql_mode;
+-----------------------------------------------------------------------------------+
| @@sql_mode |
+-----------------------------------------------------------------------------------+
| ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION |
+-----------------------------------------------------------------------------------+

-- 插入50个字符
[yejr@imysql.com]> insert into t_char select 0, repeat('x',50);
ERROR 1406 (22001): Data too long for column 'uname' at row 1

-- 修改本 session 的 sql_mode
[yejr@imysql.com]> set sql_mode = 'ONLY_FULL_GROUP_BY,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION';
Query OK, 0 rows affected (0.00 sec)

-- 去掉 STRICT_TRANS_TABLES 模式后
[yejr@imysql.com]> select @@sql_mode;
+---------------------------------------------------------------+
| @@sql_mode |
+---------------------------------------------------------------+
| ONLY_FULL_GROUP_BY,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION |
+---------------------------------------------------------------+

[yejr@imysql.com]> insert into t_char select 0, repeat('x',50);
Query OK, 1 row affected, 1 warning (0.00 sec)  -- 提示有告警信息
Records: 1 Duplicates: 0 Warnings: 1

[yejr@imysql.com]> show warnings;
+---------+------+--------------------------------------------+
| Level | Code | Message |
+---------+------+--------------------------------------------+
| Warning | 1265 | Data truncated for column 'uname' at row 1 |
+---------+------+--------------------------------------------+

因为 uname 字段的长度为 40 个字符。

2、优化online操作例如修改buffer pool修改索引名(非主键)、修改REPLICATION FILTER、修改MASTER而无需关闭SLAVE线程 等众多特性。

可以在线修改buffer pool对DBA来说实在太方便了,实例运行过程中可以动态调整,避免事先分配不合理的情况,不过 innodb_buffer_pool_instances 不能修改,而且在 innodb_buffer_pool_instances 大于 1 时,也不能将 buffer pool 调整到 1GB 以内,需要稍加注意。

如果是加大buffer pool,其过程大致是:

1、以innodb_buffer_pool_chunk_size为单位,分配新的内存pages;
2、扩展buffer pool的AHI(adaptive hash index)链表,将新分配的pages包含进来;
3、将新分配的pages添加到free list中;

如果是缩减buffer pool,其过程则大致是:

1、重整buffer pool,准备回收pages;
2、以innodb_buffer_pool_chunk_size为单位,释放删除这些pages(这个过程会有一点点耗时);
3、调整AHI链表,使用新的内存地址。

实际测试时,发现在线修改 buffer poo 的代价并不大,SQL命令提交完毕后都是瞬间完成,而后台进程的耗时也并不太久。在一个并发128线程跑tpcc压测的环境中,将 buffer pool 从32G扩展到48G,后台线程耗时 3秒,而从 48G 缩减回 32G 则耗时 18秒,期间压测的事务未发生任何锁等待。

-- 演示1:从 1G 扩大到 16G
[yejr@imysql.com]> SET GLOBAL innodb_buffer_pool_size = 51539607552; 
Query OK, 0 rows affected (0.00 sec)

-- 看看日志记录
09:21:19.460543Z 0 [Note] InnoDB: Resizing buffer pool from 1073741824 to 17179869184. (unit=134217728)
09:21:19.468069Z 0 [Note] InnoDB: disabled adaptive hash index.
09:21:20.760724Z 0 [Note] InnoDB: buffer pool 0 : 60 chunks (491511 blocks) were added.
09:21:21.922869Z 0 [Note] InnoDB: buffer pool 1 : 60 chunks (491520 blocks) were added.
09:21:21.935114Z 0 [Note] InnoDB: buffer pool 0 : hash tables were resized.
09:21:21.947264Z 0 [Note] InnoDB: buffer pool 1 : hash tables were resized.
09:21:22.203031Z 0 [Note] InnoDB: Resized hash tables at lock_sys, adaptive hash index, dictionary.
09:21:22.203062Z 0 [Note] InnoDB: Completed to resize buffer pool from 1073741824 to 17179869184.
09:21:22.203075Z 0 [Note] InnoDB: Re-enabled adaptive hash index.

-- 演示2:从 16G 缩减到 1G
[yejr@imysql.com]> SET GLOBAL innodb_buffer_pool_size = 1073741824;
Query OK, 0 rows affected (0.00 sec)

-- 看看日志记录
09:22:55.591669Z 0 [Note] InnoDB: Resizing buffer pool from 17179869184 to 1073741824. (unit=134217728)
09:22:55.680836Z 0 [Note] InnoDB: disabled adaptive hash index.
09:22:55.680864Z 0 [Note] InnoDB: buffer pool 0 : start to withdraw the last 491511 blocks.
09:22:55.765778Z 0 [Note] InnoDB: buffer pool 0 : withdrew 489812 blocks from free list. Tried to relocate 1698 pages (491510/491511).
09:22:55.774492Z 0 [Note] InnoDB: buffer pool 0 : withdrew 0 blocks from free list. Tried to relocate 1 pages (491511/491511).
09:22:55.782745Z 0 [Note] InnoDB: buffer pool 0 : withdrawn target 491511 blocks.
09:22:55.782786Z 0 [Note] InnoDB: buffer pool 1 : start to withdraw the last 491520 blocks.
09:22:55.892068Z 0 [Note] InnoDB: buffer pool 1 : withdrew 489350 blocks from free list. Tried to relocate 2166 pages (491517/491520).
09:22:55.900743Z 0 [Note] InnoDB: buffer pool 1 : withdrew 0 blocks from free list. Tried to relocate 2 pages (491519/491520).
09:22:55.908257Z 0 [Note] InnoDB: buffer pool 1 : withdrew 0 blocks from free list. Tried to relocate 0 pages (491519/491520).
09:22:55.915778Z 0 [Note] InnoDB: buffer pool 1 : withdrew 0 blocks from free list. Tried to relocate 1 pages (491520/491520).
09:22:55.923836Z 0 [Note] InnoDB: buffer pool 1 : withdrawn target 491520 blocks.
09:22:56.149172Z 0 [Note] InnoDB: buffer pool 0 : 60 chunks (491511 blocks) were freed.
09:22:56.308997Z 0 [Note] InnoDB: buffer pool 1 : 60 chunks (491520 blocks) were freed.
09:22:56.316258Z 0 [Note] InnoDB: buffer pool 0 : hash tables were resized.
09:22:56.324027Z 0 [Note] InnoDB: buffer pool 1 : hash tables were resized.
09:22:56.393589Z 0 [Note] InnoDB: Resized hash tables at lock_sys, adaptive hash index, dictionary.
09:22:56.393616Z 0 [Note] InnoDB: Completed to resize buffer pool from 17179869184 to 1073741824.
09:22:56.393628Z 0 [Note] InnoDB: Re-enabled adaptive hash index.

 

再来看下在线修改非主键索引名,直接用 ALTER TABLE RENAME INDEX 语法即可。

【新特性实践】

例如下面的SQL语法:

[yejr@imysql.com]> ALTER TABLE orders RENAME INDEX idx1 TO idxxx1; 

Query OK, 0 rows affected (0.11 sec)
Records: 0 Duplicates: 0 Warnings: 0

可以看到,几乎瞬间完成,尽管我在执行这个SQL时正跑着64个并发tpcc压测。

 

延伸阅读:

整理的比较仓促,若有遗漏或失误,请留言回复,谢谢!

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

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

 

 

PCIe-SSD卡下的xfs vs ext4对比fileio及TpmC测试

此前听说过某公司把xfs都替换成了ext4,感觉有些诧异,才有了本次测试。此次测试本意是想对比下xfs和ext4的差异,看看xfs是否还一如既往的靠谱。在测试的过程中,我还顺带测试了下MySQL 5.7.7和Percona-5.6.23的对比。

最终的建议写在前面:
1、可以继续放心大胆的采用xfs文件系统
2、使用xfs文件系统时,无需刻意加一些优化参数,默认的(defaults,noatime,nodiratime,nobarrier)就够了;
3、可以继续优先选择Percona分支版本,除了thread pool,它还有其他很多优秀的特性(MySQL官方暂无计划把thread pool功能移植到社区版);
4、 MySQL 5.7版本同样非常值得期待,但更希望加入Percona/MariaDB已经采用的、成熟的优秀补丁(thread pool、slowlog、锁/mutex拆分等);
5、在一些高IOPS设备服务器上跑MySQL时,建议采用多实例模式,不建议只运行一个实例,尽可能发挥服务器的最大性能。

来看看下面的测试结果:

1、fio测试显示的iops以及I/O吞吐能力

pcie-ssd-xfs-vs-ext4-fio-iops

pcie-ssd-xfs-vs-ext4-fio-bw

2、再来看下在做fio测试时,I/O设备的iowait以及%util值,还有PCIe-SSD卡监控到的写延迟数据:

pcie-ssd-xfs-vs-ext4-fio-iowait

pcie-ssd-xfs-vs-ext4-fio-util

pcie-xfs-vs-ext4-fio-writelatency

3、以及最终及各个不同测试模式下的MySQL tps结果对比:

pcie-ssd-xfs-vs-ext4-oltp-tpmc

小结:
1、 在%util基本相差不大的情况下,xfs相对ext4拥有绝对优势以及更好的IOPS提升空间
2、 xfs没必要刻意进行优化,默认的参数就足够了。

欢迎各位同行们也分享下您在PCIe-SSD设备下相关测试结果,相互借鉴下。

上面图表中几个不同颜色分别表示含义:

1) 蓝色:表示经过优化的xfs
格式化时的参数:
mkfs.xfs -d agcount=256 -l size=128m,lazy-count=1,version=2 /dev/diska1

mount时的参数
defaults,noatime,nodiratime,nobarrier,discard,allocsize=256m,logbufs=8,attr2,logbsize=256k

2) 灰色:表示默认的xfs
格式化时的参数:
mkfs.xfs -f -L /data /dev/diska1

mount时的参数
defaults,noatime,nodiratime,nobarrier

3) 黄色:表示ext4。
格式化时的参数:
mkfs.ext4 /dev/diska2

mount时的参数:
defaults,noatime,nodiratime,nobarrier
延伸阅读:
1、MySQL 5.6.17/Percona5.6.16/MariaDB 10.0.11/OneSQL 5.6.16 TpmC测试
2、tpcc-mysql安装、使用、结果解读
3、SAS vs SSD各种模式下MySQL TPCC OLTP对比测试结果
4、XFS设计

[MySQL FAQ]系列 — 什么情况下会用到临时表

MySQL在以下几种情况会创建临时表:

1、UNION查询;
2、用到TEMPTABLE算法或者是UNION查询中的视图;
3、ORDER BY和GROUP BY的子句不一样时;
4、表连接中,ORDER BY的列不是驱动表中的;
5、DISTINCT查询并且加上ORDER BY时;
6、SQL中用到SQL_SMALL_RESULT选项时;
7、FROM中的子查询;
8、子查询或者semi-join时创建的表;

EXPLAIN 查看执行计划结果的 Extra 列中,如果包含 Using Temporary 就表示会用到临时表。

当然了,如果临时表中需要存储的数据量超过了上限( tmp-table-sizemax-heap-table-size 中取其小者),这时候就需要生成基于磁盘的临时表了。

在以下几种情况下,会创建磁盘临时表:

1、数据表中包含BLOB/TEXT列;
2、在 GROUP BY 或者 DSTINCT 的列中有超过 512字符 的字符类型列(或者超过 512字节的 二进制类型列,在5.6.15之前只管是否超过512字节);
3、在SELECT、UNION、UNION ALL查询中,存在最大长度超过512的列(对于字符串类型是512个字符,对于二进制类型则是512字节);
4、执行SHOW COLUMNS/FIELDS、DESCRIBE等SQL命令,因为它们的执行结果用到了BLOB列类型。

从5.7.5开始,新增一个系统选项 internal_tmp_disk_storage_engine 可定义磁盘临时表的引擎类型为 InnoDB,而在这以前,只能使用 MyISAM。而在5.6.3以后新增的系统选项 default_tmp_storage_engine 是控制 CREATE TEMPORARY TABLE 创建的临时表的引擎类型,在以前默认是MEMORY,不要把这二者混淆了。

见下例:

mysql> set default_tmp_storage_engine = "InnoDB";
-rw-rw----   1 mysql mysql  8558 Jul  7 15:22 #sql4b0e_10_0.frm -- InnoDB引擎的临时表
-rw-rw----   1 mysql mysql 98304 Jul  7 15:22 #sql4b0e_10_0.ibd
-rw-rw----   1 mysql mysql  8558 Jul  7 15:25 #sql4b0e_10_2.frm

mysql> set default_tmp_storage_engine = "MyISAM";
-rw-rw----   1 mysql mysql     0 Jul  7 15:25 #sql4b0e_10_2.MYD -- MyISAM引擎的临时表
-rw-rw----   1 mysql mysql  1024 Jul  7 15:25 #sql4b0e_10_2.MYI

mysql> set default_tmp_storage_engine = "MEMORY";
-rw-rw----   1 mysql mysql  8558 Jul  7 15:26 #sql4b0e_10_3.frm -- MEMORY引擎的临时表

 

延伸阅读:

[MySQL优化案例]系列 — 频繁创建临时表

无需过分关注Created_tmp_disk_tables

8.4.4 How MySQL Uses Internal Temporary Tables

MySQL 5.7版本新特性连载(三)

本文是基于MySQL-5.7.7-rc版本,未来可能 还会发生更多变化。本节开始讲5.7版本中的新特性。

1、安全性
a. 用户表 mysql.user 的 plugin字段不允许为空, 默认值是 mysql_native_password,而不是 mysql_old_password,不再支持旧密码格式;
b. 增加密码过期机制,过期后需要修改密码,否则可能会被禁用,或者进入沙箱模式;
c. 使用 mysql_install_db 初始化时,默认会自动生成随机密码,并且不创建除 root@localhost 外的其他账号,也不创建 test 库;

【新特性实践】

执行 mysql_install_db 进行新实例初始化:

[yejr@imysql.com]# ./bin/mysql_install_db --user=mysql --datadir=/data/mysql/

2015-06-24 13:55:29 [WARNING] mysql_install_db is deprecated. Please consider switching to mysqld --initialize
2015-06-24 13:55:38 [ERROR]   Child process: /opt/17173_install/mysql-5.7.7-rc-linux-glibc2.5-x86_64/bin/mysqld terminated prematurely with errno= 32
2015-06-24 13:55:38 [ERROR]   Failed to execute /opt/17173_install/mysql-5.7.7-rc-linux-glibc2.5-x86_64/bin/mysqld --bootstrap --datadir=/data/mysql --lc-messages-dir=/usr/share/mysql --lc-messages=en_US
-- server log begin --
mysqld: [Warning] --bootstrap is deprecated. Please consider using --initialize instead
-- server log end --

可以看到提示 mysql_install_db 已经不再推荐使用了,建议改成 mysqld –initialize 完成实例初始化。

改成 mysqld –initialize 后,如果 datadir 指向的目标目录下已经有数据文件,则会有类似提示:

[yejr@imysql.com]#./bin/mysqld --user=mysql --basedir=/opt/17173_install/mysql-5.7.7-rc-linux-glibc2.5-x86_64/ --datadir=/data/mysql --initial --initialize-insecure

2015-06-24T06:05:05.533588Z 0 [ERROR] --initialize specified but the data directory has files in it. Aborting.
2015-06-24T06:05:05.533627Z 0 [ERROR] Aborting

因此,需要先确保 datadir 目标目录下是空的,避免误操作破坏已有数据。

另外,在初始化时如果加上 –initial-insecure,则会创建空密码的 root@localhost 账号,否则会创建带密码的 root@localhost 账号,密码直接写在 log-error 日志文件中(在5.6版本中是放在 ~/.mysql_secret 文件里,更加隐蔽,不熟悉的话可能会无所适从)

[yejr@imysql.com]#./bin/mysqld --user=mysql --basedir=/opt/17173_install/mysql-5.7.7-rc-linux-glibc2.5-x86_64/ --datadir=/data/mysql --initial

2015-06-24T06:14:31.458905Z 0 [Warning] Insecure configuration for --secure-file-priv: Current value does not restrict location of generated files. Consider setting it to a valid, non-empty path.

初始化完毕后,如果没使用新版本的客户端登入,还会报告类似下面的错误:

mysql -uroot -p
Enter password:
ERROR 1862 (HY000): Your password has expired. To log in you must change it using a client that supports expired passwords.

上面的错误提示意思是需要用当前版本的客户端登入,因为新用户登入后需要立刻修改密码,否则无法继续后续的工作:

[(root@imysql.com)]>use mysql
ERROR 1820 (HY000): You must SET PASSWORD before executing this statement

[(root@imysql.com)]>set password = password('abcd');
Query OK, 0 rows affected, 1 warning (0.00 sec)

修改完密码后,就可以继续使用旧版本的客户端工具了。

下一期,我们继续讲讲5.7的其他新特性。

 

延伸阅读:

整理的比较仓促,若有遗漏或失误,请留言回复,谢谢!

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

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

 

 

MySQL 5.7版本新特性连载(二)

本文将和大家一起分享下5.7的新特性,不过我们要先从即将被删除的特性以及建议不再使用的特性说起。根据这些情况,我们在新版本及以后的版本中,应该不再使用,避免未来产生兼容性问题。

本文是基于MySQL-5.7.7-rc版本,未来可能 还会发生更多变化。

2、预计取消的特性
2.1、客户端交互协议中,EOF协议包不建议再使用了,建议改成OK协议包。WL#7766
2.2、不建议使用SHOW PROFILE指令,或直接从INFORMATION_SCHEMA.PROFILING中查看,建议利用PERFORMANCE_SCHEMA中的几个视图查看。WL#6802
2.2、基于DES算法的加解密函数不建议使用, 取而代之的是用基于AES的加解密函数。WL#8126
2.4、同上,还不建议使用ENCODE()/DECODE()函数。WL#6984
2.5、采用ALTER USER来为用户修改密码,不建议再使用SET PASSWORD修改密码。WL#6409
2.6、和InnoDB相关的4个系统参数innodb_large_prefix、innodb_file_format、innodb_file_format_check、innodb_file_format_max不再建议使用。WL#7703
2.7、不再建议在EXPLAIN后面加上EXTENDED/PARTITIONS关键字。WL#7027
2.8、不再建议使用collation_database、character_set_database系统参数。WL#2.11
2.9、不再建议使用sync_frm系统参数。WL#8216
2.10、不再建议使用@@session.gtid_executed系统变量。WL#7518

下一期,我们讲讲5.7的新特性。

延伸阅读:

整理的比较仓促,若有遗漏或失误,请留言回复,谢谢!

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

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