【问题标题】:Optimizing MySQL indexes for query (trading tick data database)优化 MySQL 索引以进行查询(交易分时数据数据库)
【发布时间】:2017-07-29 01:53:33
【问题描述】:

我的 MySQL 数据库有超过 3.5 亿行,并且还在增长。它现在的大小是 32GB。我正在使用 SSD 和大量 RAM,但想寻求建议以确保我使用了适当的索引。

CREATE TABLE `qcollector` (
  `key` bigint(20) NOT NULL AUTO_INCREMENT,
  `instrument` char(4) DEFAULT NULL,
  `datetime` datetime DEFAULT NULL,
  `last` double DEFAULT NULL,
  `lastsize` int(10) DEFAULT NULL,
  `totvol` int(10) DEFAULT NULL,
  `bid` double DEFAULT NULL,
  `ask` double DEFAULT NULL,
  PRIMARY KEY (`key`),
  KEY `datetime_index` (`datetime`)
) ENGINE=InnoDB;

show index from qcollector;
+------------+------------+----------------+--------------+-------------+-----------+--    -----------+----------+--------+------+------------+---------+---------------+
| Table      | Non_unique | Key_name       | Seq_in_index | Column_name | Collation |     Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+------------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| qcollector |          0 | PRIMARY        |            1 | key         | A         |   378866659 |     NULL | NULL   |      | BTREE      |         |               |
| qcollector |          1 | datetime_index |            1 | datetime    | A         |    63144443 |     NULL | NULL   | YES  | BTREE      |         |               |
+------------+------------+----------------+--------------+-------------+-----------+------    -------+----------+--------+------+------------+---------+---------------+
2 rows in set (0.03 sec)

select * from qcollector order by datetime desc limit 1;
+-----------+------------+---------------------+---------+----------+---------+---------+--------+
| key       | instrument | datetime            | last    | lastsize | totvol  | bid     | ask    |
+-----------+------------+---------------------+---------+----------+---------+---------+--------+
| 389054487 | ES         | 2012-06-29 15:14:59 | 1358.25 |        2 | 2484771 | 1358.25 | 1358.5 |
+-----------+------------+---------------------+---------+----------+---------+---------+--------+
1 row in set (0.09 sec)

一个典型的慢查询(全表扫描,这个查询需要3-4分钟):

explain select date(datetime), count(lastsize) from qcollector where instrument = 'ES' and datetime > '2011-01-01' and time(datetime) between '15:16:00' and '15:29:00' group by date(datetime) order by date(datetime) desc;
+------+-------------+------------+------+----------------+------+---------+------+-----------+----------------------------------------------+
| id   | select_type | table      | type | possible_keys  | key  | key_len | ref  | rows      | Extra                                        |
+------+-------------+------------+------+----------------+------+---------+------+-----------+----------------------------------------------+
|    1 | SIMPLE      | qcollector | ALL  | datetime_index | NULL | NULL    | NULL | 378866659 | Using where; Using temporary; Using filesort |
+------+-------------+------------+------+----------------+------+---------+------+-----------+----------------------------------------------+

【问题讨论】:

    标签: mysql optimization indexing create-table


    【解决方案1】:

    有几个想法供您考虑:

    • 覆盖索引(即包含查询中引用的所有列的索引)可能会有所帮助。这样的索引将需要更多的磁盘(SSD?)空间,但它将消除 MySQL 访问数据页以查找不在索引中的列的值的必要性。

      ON qcollector (datetime,instrument,lastsize)

      ON qcollector (instrument,datetime,lastsize)

    • 您真的需要从计数中排除 lastsize 具有 NULL 值的行吗?你可以返回所有行的计数吗?如果您可以改为返回COUNT(1)SUM(1),则查询不需要引用lastsize 列,因此索引中不需要它来使其成为覆盖索引。

      COUNT(lastsize) 表达式等价于SUM(IF(lastsize IS NULL,0,1))

    • 当日期时间范围只有 NULL lastsize 值时,您是否需要返回日期,或者是否可以排除所有具有 NULL lastsize 的行?也就是说,你可以包含一个谓词像

      AND lastsize IS NOT NULL

    在您的查询中?

    这些可能会有所帮助。


    我认为最大的问题是TIME(datetime) 表达式上的谓词不可分割。也就是说,MySQL 不会对它们使用索引范围扫描操作。 datetime 裸列上的谓词是 sargable... 这就是 EXPLAIN 将 datetime_index 显示为可能键的原因。

    另外一个大问题是查询对派生表达式执行GROUP BYORDER BY操作,这将需要MySQL生成一个中间结果集(作为临时MyISAM表),然后处理那个结果集。当需要处理大量行时,这可能会带来很多繁重的工作。


    就表更改而言,我会考虑使用单独的 DATE 和 TIME 列,并使用 TIMESTAMP 数据类型代替 DATETIME(如果您需要将日期和时间存储在一起)。我将重写查询以引用裸 DATE 和裸 TIME 列,并考虑添加一个覆盖索引,其中包含重写查询中引用的所有列,前导列是具有最高基数的列(并且在查询。)

    【讨论】:

    • 如果我添加了一个覆盖索引(比如 datetime、lastsize)[或新建议的结构],但查询不使用这些列之一,是否仍会使用该索引?还是在这种情况下我需要两个单独的索引?
    • 关于使用 COUNT,我经常使用 SUM - 通常在特定时间范围内再次使用
    • @user1530260:你想要一个包含所有列的索引。单独列上的索引对您的查询没有帮助。 (它们可能对其他查询有用。)但是对于您的查询,您需要一个索引。
    【解决方案2】:

    当您在列上使用datetime 函数时,无法有效使用索引。您还可以将日期和时间存储在单独的列中并为其编制索引,但这会占用更多存储空间。

    您可能还需要考虑添加多列索引。 (instrument, datetime) 上的索引可能会对您有所帮助。

    【讨论】:

    • 大多数查询使用 'yyyy-mm-dd hh:mm:ss' 和 'yyyy-mm-dd hh:mm:ss' 之间的日期时间(仅一两天),所以我发现对日期时间使用单个列而不是两个单独的列要好得多。但有些查询(如上)跨越数月或数年,我只需要在每天的特定时间 (hh:mm:ss) 包括在内。
    • “仪器”少于 10 个,我的理解是在这种情况下索引不会有帮助。
    • 如果您想要一个跨越数年但每天只选择几次的快速查询,您需要对时间进行索引。不幸的是,在 MySQL 中,唯一的方法是创建一个新列,因为它是 doesn't support functional indexes
    • 谢谢。有趣的是(或没那么有趣......)我开始使用两列作为日期/时间,但后来认为它效率低下,所以将它们组合起来。
    • @user1530260:当我们想要从连续的日期时间范围内检索行时,我们通常希望日期和时间在一个列中,例如WHERE datetime >= '2012-07-14 08:00:00' AND datetime < '2012-07-14 17:00:00'。在您的情况下,您分别处理 DATE 和 TIME 部分,因此单独的列可能是要走的路。您在整整几天中检索相同的小时间窗口。对于您的查询,您的索引应包含两列。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-12
    • 1970-01-01
    • 1970-01-01
    • 2015-03-25
    • 2015-01-18
    相关资源
    最近更新 更多