优化慢速索引 Select MySql 查询
Optimizing Slow, Indexed Select MySql Query
我正在尝试使用在 src_ip 上建立索引的 table 执行一个简单的 select 查询,如下所示:
SELECT * 来自 netflow_nov2 其中 src_IP
=3111950672;
然而,这甚至在 4 或 5 小时后仍未完成。我需要响应在几秒钟的范围内。我想知道如何优化它,所以就是这种情况。
另请注意,源 ip 已使用内置 SQL 命令转换为整数。
关于 table 的其他信息:
table 包含从 nfdump 解析的 netflow 数据。我正在使用 table 获取有关特定 IP 地址的信息。也就是说,基本上只会用到像上面这样的查询。
这是 SHOW TABLE STATUS 为此 table 给出的相关信息:
Rows: 4,205,602,143 (4 billion)
Data Length: 426,564,911,104 (426 GB)
Index Length: 57,283,706,880 (57 GB)
系统信息:
硬盘:~2TB,接近最大使用
内存:64GB
my.cnf 文件:
见要点:https://gist.github.com/ashtonwebster/e0af038101e1b42ca7e3
Table结构:
mysql> DESCRIBE netflow_nov2;
+-----------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+------------------+------+-----+---------+-------+
| date | datetime | YES | MUL | NULL | |
| duration | float | YES | | NULL | |
| protocol | varchar(16) | YES | | NULL | |
| src_IP | int(10) unsigned | YES | MUL | NULL | |
| src_port | int(2) | YES | | NULL | |
| dest_IP | int(10) unsigned | YES | MUL | NULL | |
| dest_port | int(2) | YES | | NULL | |
| flags | varchar(8) | YES | | NULL | |
| Tos | int(4) | YES | | NULL | |
| packets | int(8) | YES | | NULL | |
| bytes | int(8) | YES | | NULL | |
| pps | int(8) | YES | | NULL | |
| bps | int(8) | YES | | NULL | |
| Bpp | int(8) | YES | | NULL | |
| Flows | int(8) | YES | | NULL | |
+-----------+------------------+------+-----+---------+-------+
15 rows in set (0.02 sec)
我有关于索引和解释结果的额外信息,但简单地说:
-索引是b-tree,有date、src_ip、dest_ip的索引,但真正会用到的只有src_ip
-根据 EXPLAIN 的输出,src_ip 索引用于顶部提到的特定查询
以及mysqltuner的输出:
见要点:https://gist.github.com/ashtonwebster/cbfd98ee1799a7f6b323
显示创建 TABLE 输出:
| netflow_nov2 | CREATE TABLE `netflow_nov2` (
`date` datetime DEFAULT NULL,
`duration` float DEFAULT NULL,
`protocol` varchar(16) DEFAULT NULL,
`src_IP` int(10) unsigned DEFAULT NULL,
`src_port` int(2) DEFAULT NULL,
`dest_IP` int(10) unsigned DEFAULT NULL,
`dest_port` int(2) DEFAULT NULL,
`flags` varchar(8) DEFAULT NULL,
`Tos` int(4) DEFAULT NULL,
`packets` int(8) DEFAULT NULL,
`bytes` int(8) DEFAULT NULL,
`pps` int(8) DEFAULT NULL,
`bps` int(8) DEFAULT NULL,
`Bpp` int(8) DEFAULT NULL,
`Flows` int(8) DEFAULT NULL,
KEY `src_IP` (`src_IP`),
KEY `dest_IP` (`dest_IP`),
KEY `date` (`date`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 |
提前致谢
我认为阅读 table 没有 索引将花费不到 5 小时。但是你确实有一个大table。有两种 "environmental" 可能会破坏性能:
- table 被另一个进程锁定。
- 结果集很大(数千万行),返回结果集的网络 latency/processing 时间是问题所在。
不过,我的第一个猜测是查询 没有 使用索引。一开始我错过了这个,但你有一个多部分索引。此查询可以利用的 only 索引是第一个键为 src_IP
的索引。因此,如果您的索引是 netflow_nov2(src_IP, date, dest_ip)
或 netflow_nov2(src_IP, dest_ip, date)
,那么您就可以了。如果其他列中的任何一个列在前面,则不会使用该索引。您可以通过将 explain
放在查询前面以查看是否正在使用索引来轻松查看发生了什么。
如果这是一个问题,请创建一个索引 src_IP
作为索引中的第一个(或唯一)键。
您当前的 table 结构针对随机写入进行了优化:记录按写入顺序放置在磁盘上。
不幸的是,这种结构很好支持的唯一读取模式是全table扫描。
使用非覆盖二级索引仍然会导致大量随机磁盘寻道,这会降低性能。
当数据以与其在磁盘上的位置相同的顺序读取时,可以获得最佳的读取性能,对于 InnoDB 来说,这意味着以主键顺序。
实体化视图(另一个具有适当主键的 InnoDB table)可能是一个可能的解决方案。在这种情况下,需要以 src_IP
开头的主键。
upd: 思路是实现数据局部性,避免随机磁盘IO,实现顺序读。这意味着您的物化视图将如下所示:
CREATE TABLE `netflow_nov2_view` (
`row_id` bigint not null, -- see below
`date` datetime DEFAULT NULL,
`duration` float DEFAULT NULL,
`protocol` varchar(16) DEFAULT NULL,
`src_IP` int(10) unsigned DEFAULT NULL,
`src_port` int(2) DEFAULT NULL,
`dest_IP` int(10) unsigned DEFAULT NULL,
`dest_port` int(2) DEFAULT NULL,
`flags` varchar(8) DEFAULT NULL,
`Tos` int(4) DEFAULT NULL,
`packets` int(8) DEFAULT NULL,
`bytes` int(8) DEFAULT NULL,
`pps` int(8) DEFAULT NULL,
`bps` int(8) DEFAULT NULL,
`Bpp` int(8) DEFAULT NULL,
`Flows` int(8) DEFAULT NULL,
PRIMARY KEY (`src_IP`, `row_id`) -- you won't need other keys
) ENGINE=InnoDB DEFAULT CHARSET=latin1
其中 row_id
必须由您的具体化逻辑维护,因为您在原始 table 中没有它(或者您可以在您的原来table,反正InnoDB就是这么处理的)。
关键的区别是现在磁盘上的所有数据都按主键顺序放置,这意味着一旦找到具有给定 'src_IP' 的第一条记录,所有其他记录都可以按顺序获取可能。
根据您的数据写入方式和相邻的应用程序逻辑,它可以通过触发器或某些自定义外部进程来完成。
如果可以牺牲当前的写入性能(或使用一些异步队列作为缓冲区),那么可能有一个针对读取进行优化的 table 就足够了。
关于 InnoDB 索引的更多信息:
http://dev.mysql.com/doc/refman/5.6/en/innodb-index-types.html
我正在尝试使用在 src_ip 上建立索引的 table 执行一个简单的 select 查询,如下所示:
SELECT * 来自 netflow_nov2 其中 src_IP
=3111950672;
然而,这甚至在 4 或 5 小时后仍未完成。我需要响应在几秒钟的范围内。我想知道如何优化它,所以就是这种情况。
另请注意,源 ip 已使用内置 SQL 命令转换为整数。
关于 table 的其他信息: table 包含从 nfdump 解析的 netflow 数据。我正在使用 table 获取有关特定 IP 地址的信息。也就是说,基本上只会用到像上面这样的查询。
这是 SHOW TABLE STATUS 为此 table 给出的相关信息:
Rows: 4,205,602,143 (4 billion)
Data Length: 426,564,911,104 (426 GB)
Index Length: 57,283,706,880 (57 GB)
系统信息: 硬盘:~2TB,接近最大使用 内存:64GB
my.cnf 文件: 见要点:https://gist.github.com/ashtonwebster/e0af038101e1b42ca7e3
Table结构:
mysql> DESCRIBE netflow_nov2;
+-----------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+------------------+------+-----+---------+-------+
| date | datetime | YES | MUL | NULL | |
| duration | float | YES | | NULL | |
| protocol | varchar(16) | YES | | NULL | |
| src_IP | int(10) unsigned | YES | MUL | NULL | |
| src_port | int(2) | YES | | NULL | |
| dest_IP | int(10) unsigned | YES | MUL | NULL | |
| dest_port | int(2) | YES | | NULL | |
| flags | varchar(8) | YES | | NULL | |
| Tos | int(4) | YES | | NULL | |
| packets | int(8) | YES | | NULL | |
| bytes | int(8) | YES | | NULL | |
| pps | int(8) | YES | | NULL | |
| bps | int(8) | YES | | NULL | |
| Bpp | int(8) | YES | | NULL | |
| Flows | int(8) | YES | | NULL | |
+-----------+------------------+------+-----+---------+-------+
15 rows in set (0.02 sec)
我有关于索引和解释结果的额外信息,但简单地说: -索引是b-tree,有date、src_ip、dest_ip的索引,但真正会用到的只有src_ip -根据 EXPLAIN 的输出,src_ip 索引用于顶部提到的特定查询
以及mysqltuner的输出: 见要点:https://gist.github.com/ashtonwebster/cbfd98ee1799a7f6b323
显示创建 TABLE 输出:
| netflow_nov2 | CREATE TABLE `netflow_nov2` (
`date` datetime DEFAULT NULL,
`duration` float DEFAULT NULL,
`protocol` varchar(16) DEFAULT NULL,
`src_IP` int(10) unsigned DEFAULT NULL,
`src_port` int(2) DEFAULT NULL,
`dest_IP` int(10) unsigned DEFAULT NULL,
`dest_port` int(2) DEFAULT NULL,
`flags` varchar(8) DEFAULT NULL,
`Tos` int(4) DEFAULT NULL,
`packets` int(8) DEFAULT NULL,
`bytes` int(8) DEFAULT NULL,
`pps` int(8) DEFAULT NULL,
`bps` int(8) DEFAULT NULL,
`Bpp` int(8) DEFAULT NULL,
`Flows` int(8) DEFAULT NULL,
KEY `src_IP` (`src_IP`),
KEY `dest_IP` (`dest_IP`),
KEY `date` (`date`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 |
提前致谢
我认为阅读 table 没有 索引将花费不到 5 小时。但是你确实有一个大table。有两种 "environmental" 可能会破坏性能:
- table 被另一个进程锁定。
- 结果集很大(数千万行),返回结果集的网络 latency/processing 时间是问题所在。
不过,我的第一个猜测是查询 没有 使用索引。一开始我错过了这个,但你有一个多部分索引。此查询可以利用的 only 索引是第一个键为 src_IP
的索引。因此,如果您的索引是 netflow_nov2(src_IP, date, dest_ip)
或 netflow_nov2(src_IP, dest_ip, date)
,那么您就可以了。如果其他列中的任何一个列在前面,则不会使用该索引。您可以通过将 explain
放在查询前面以查看是否正在使用索引来轻松查看发生了什么。
如果这是一个问题,请创建一个索引 src_IP
作为索引中的第一个(或唯一)键。
您当前的 table 结构针对随机写入进行了优化:记录按写入顺序放置在磁盘上。
不幸的是,这种结构很好支持的唯一读取模式是全table扫描。 使用非覆盖二级索引仍然会导致大量随机磁盘寻道,这会降低性能。
当数据以与其在磁盘上的位置相同的顺序读取时,可以获得最佳的读取性能,对于 InnoDB 来说,这意味着以主键顺序。
实体化视图(另一个具有适当主键的 InnoDB table)可能是一个可能的解决方案。在这种情况下,需要以 src_IP
开头的主键。
upd: 思路是实现数据局部性,避免随机磁盘IO,实现顺序读。这意味着您的物化视图将如下所示:
CREATE TABLE `netflow_nov2_view` (
`row_id` bigint not null, -- see below
`date` datetime DEFAULT NULL,
`duration` float DEFAULT NULL,
`protocol` varchar(16) DEFAULT NULL,
`src_IP` int(10) unsigned DEFAULT NULL,
`src_port` int(2) DEFAULT NULL,
`dest_IP` int(10) unsigned DEFAULT NULL,
`dest_port` int(2) DEFAULT NULL,
`flags` varchar(8) DEFAULT NULL,
`Tos` int(4) DEFAULT NULL,
`packets` int(8) DEFAULT NULL,
`bytes` int(8) DEFAULT NULL,
`pps` int(8) DEFAULT NULL,
`bps` int(8) DEFAULT NULL,
`Bpp` int(8) DEFAULT NULL,
`Flows` int(8) DEFAULT NULL,
PRIMARY KEY (`src_IP`, `row_id`) -- you won't need other keys
) ENGINE=InnoDB DEFAULT CHARSET=latin1
其中 row_id
必须由您的具体化逻辑维护,因为您在原始 table 中没有它(或者您可以在您的原来table,反正InnoDB就是这么处理的)。
关键的区别是现在磁盘上的所有数据都按主键顺序放置,这意味着一旦找到具有给定 'src_IP' 的第一条记录,所有其他记录都可以按顺序获取可能。
根据您的数据写入方式和相邻的应用程序逻辑,它可以通过触发器或某些自定义外部进程来完成。
如果可以牺牲当前的写入性能(或使用一些异步队列作为缓冲区),那么可能有一个针对读取进行优化的 table 就足够了。
关于 InnoDB 索引的更多信息: http://dev.mysql.com/doc/refman/5.6/en/innodb-index-types.html