优化慢速索引 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