2018年1月

以前遇到过mysql对index_merge进行优化时,按照它自己的“猜测”优化,导致性能下降的问题, 后来靠强制force index(...)关闭优化,解决了。
最近有发现一个对联合索引的“智能”优化。这导致同一个sql5.5前后的版本执行效率完全不一样。

表结构如下:

CREATE TABLE `feed` (
  `feedid` bigint(11) NOT NULL AUTO_INCREMENT,
  `userid` int(11) NOT NULL,
  `typeid` int(11) NOT NULL,
  `dataid` int(11) NOT NULL,
  `invalid` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`feedid`),
  UNIQUE KEY `dataid_typeid` (`dataid`,`typeid`),
  KEY `userid_t_i` (`userid`,`typeid`,`invalid`)
) ENGINE=TokuDB AUTO_INCREMENT=1516204596 DEFAULT CHARSET=utf8 `compression`='tokudb_zlib'

SQL语句时:

select feedid,userid,dataid,typeid from feed where userid = '25057158'   and typeid in (0,2,6)  and (invalid = 0 or invalid = 11) order by feedid desc limit 0,30 ;

按理说完全符合联合索引,应该很快能执行成功,但是实际上发现在新版mysql中有扫描全表。
explain看:

mysql> explain select feedid,userid,dataid,typeid from feed where userid = '25057158'   and typeid in (0,2,6)  and (invalid = 0 or invalid = 11) order by feedid desc limit 0,30 ;
+------+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
| id   | select_type | table | type  | possible_keys | key     | key_len | ref  | rows | Extra       |
+------+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
|    1 | SIMPLE      | feed  | index | userid_t_i    | PRIMARY | 8       | NULL |   30 | Using where |
+------+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
1 row in set (0.01 sec)

可以看到居然是用了PRIMARY去做查询,实际执行的结果也如此。完全忽略了possible_keys userid_t_i。
我猜想,是mysql发现sql中出现了多处INOR, 认为这个索引的效率是极其低下的,因此“智能”换成了它认为更高效的主键作为索引。

所以如果使用强制索引,或者没有INOR, 它不会做这个优化。 用explain再验证了这一点:

mysql> explain select feedid,userid,dataid,typeid from feed force index (userid) where userid = '178804206'   and typeid in (0,2,6)  and (invalid = 0 or invalid = 11) order by feedid desc limit 0,30;
+------+-------------+-------+-------+---------------+------------+---------+------+------+-----------------------------+
| id   | select_type | table | type  | possible_keys | key        | key_len | ref  | rows | Extra                       |
+------+-------------+-------+-------+---------------+------------+---------+------+------+-----------------------------+
|    1 | SIMPLE      | feed  | range | userid_t_i    | userid_t_i | 12      | NULL | 2076 | Using where; Using filesort |
+------+-------------+-------+-------+---------------+------------+---------+------+------+-----------------------------+

mysql> explain select feedid,userid,dataid,typeid from feed where userid = '25057158'   and typeid = 0  and invalid = 0  order by feedid desc limit 0,30 ;
+------+-------------+-------+------+---------------+------------+---------+-------------------+------+-------------+
| id   | select_type | table | type | possible_keys | key        | key_len | ref               | rows | Extra       |
+------+-------------+-------+------+---------------+------------+---------+-------------------+------+-------------+
|    1 | SIMPLE      | feed  | ref  | userid_t_i    | userid_t_i | 12      | const,const,const |  585 | Using where |
+------+-------------+-------+------+---------------+------------+---------+-------------------+------+-------------+

此外, 这个优化也应该是由于sql末尾有order by引起的, 如果没有排序,应该不会扫描全表:

mysql> explain select feedid,userid,dataid,typeid from feed where userid = '25057158'   and typeid in (0,2,6)  and (invalid = 0 or invalid = 11)  limit 0,30 ;
+------+-------------+-------+-------+---------------+------------+---------+------+------+-------------+
| id   | select_type | table | type  | possible_keys | key        | key_len | ref  | rows | Extra       |
+------+-------------+-------+-------+---------------+------------+---------+------+------+-------------+
|    1 | SIMPLE      | feed  | range | userid_t_i    | userid_t_i | 12      | NULL |  591 | Using where |
+------+-------------+-------+-------+---------------+------------+---------+------+------+-------------+
1 row in set (0.00 sec)

而如果把order by feedid desc去掉的sql真实的执行一遍以后,因为有了缓存,再次去explain第一个sql的时候,居然不提示用PRIMARY key了,难道是一个智能学习的过程? 而实际的执行时也是只用了userid_t_i作为索引。

mysql> explain select feedid,userid,dataid,typeid from feed where userid = '25057158'   and typeid in (0,2,6)  and (invalid = 0 or invalid = 11)  limit 0,30 ;
+------+-------------+-------+-------+---------------+------------+---------+------+------+-------------+
| id   | select_type | table | type  | possible_keys | key        | key_len | ref  | rows | Extra       |
+------+-------------+-------+-------+---------------+------------+---------+------+------+-------------+
|    1 | SIMPLE      | feed  | range | userid_t_i    | userid_t_i | 12      | NULL |    7 | Using where |
+------+-------------+-------+-------+---------------+------------+---------+------+------+-------------+

总之,我猜测是mysql不同版本对optimizer_switch等参数的不同调整导致的, 也许可以在mysql文档中找出问题并解决。
比如: https://dev.mysql.com/doc/refman/5.7/en/index-merge-optimization.html

最近ArchLinux的内核升级加入了针对Meltdown的补丁。正好测试一下为了这个安全性而对性能产生了多大的影响。
用了最简单的小测试代码meltdown_test.c,测试极端情况:

#include <syscall.h>
#include <unistd.h>
#include <stdio.h>
int main(void) {

    for (int i=0; i< (1<<27) ;i++){
        syscall(SYS_time);
    }

    return 0;
}

使用相同的内核版本:4.14.12-1
在不启用补丁的情况下:

$ time ./meltdown_test 

real    0m5.761s
user    0m2.421s
sys    0m3.340s

在启用补丁的情况下:

$ time ./meltdown_test 

real    0m23.715s
user    0m11.745s
sys    0m11.834s

linux的内核补丁是通过PTI(Page Table Isolation)实现的。如果只从测试结果看,对性能的影响还是很明显的。这个补丁,主要是增加了用户态/内核态切换的性能开销,因此,对于有频繁线程切换或系统调用的服务影响会很大, 比如mysql,nginx,redis这些最常见的服务,以及服务器的文件I/O能力等等。

个人用户来说,最好的做法就是 关闭补丁。

今天更新系统的时候发现升级了一个libnghttp2库文件,好奇查看了一下:

pacman -Qi libnghttp2
名字           : libnghttp2
版本           : 1.29.0-1
描述           : Framing layer of HTTP/2 is implemented as a reusable C library
架构           : x86_64
URL            : https://nghttp2.org/
软件许可       : MIT
组             : 无
提供           : 无
依赖于         : glibc
可选依赖       : 无
要求被         : curl
被可选依赖     : 无
冲突与         : nghttp2<1.20.0-2
取代           : 无
安装后大小     : 337.00 KiB
打包者         : Jan de Groot <jgc@archlinux.org>
编译日期       : 2017年12月26日 星期二 06时39分21秒
安装日期       : 2018年01月05日 星期五 21时23分48秒
安装原因       : 作为其他软件包的依赖关系安装
安装脚本       : 否
验证者         : 数字签名

原来是被curl依赖的, 我意识到curl已经支持http2了,测试一下,果然:

curl -v --http2 https://kexiao8.com/
...
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x562250ec3160)
> GET / HTTP/2
> Host: kexiao8.com
> User-Agent: curl/7.57.0
> Accept: */*

看一下官方文档:Starting in 7.43.0, libcurl fully supports HTTP/2 multiplexing, which is the term for doing multiple independent transfers over the same physical TCP connection.
`curl offers the --http2 command line option to enable use of HTTP/2.
Since 7.47.0, the curl tool enables HTTP/2 by default for HTTPS connections.`