【问题标题】:Improve speed execution of MySQL select query提高 MySQL 选择查询的执行速度
【发布时间】:2018-05-17 21:11:54
【问题描述】:

我有 2 个包含数百万行的 MySQL 表,我正在尝试执行查询选择以从两个表中获取特定数据列。尽管我的第一个良好期望是查询选择的执行需要几秒钟(大约 5 秒),并且索引应用于 WHERE 条件。

为 2 个 MySQL 表创建语句:

CREATE TABLE `T1` (
  `T1_id` int(15) NOT NULL AUTO_INCREMENT, 
  `T1_val1` varchar(45) NOT NULL,
  `T1_val2` varchar(45) NOT NULL,
  `T1_val3` bigint(11) NOT NULL,
  `T1_val4` datetime NOT NULL, 
  `T1_val5` varchar(100) NOT NULL, 
  `T1_val6` float NOT NULL,
  `T1_val7` datetime NOT NULL,
  `T1_val8` varchar(100) NOT NULL,
  `T1_val9` varchar(100) NOT NULL, 
  `T1_val10` varchar(100) NOT NULL,
  PRIMARY KEY (`T1_id`),
  KEY `T1_val4` (`T1_val4`)
) ENGINE=InnoDB AUTO_INCREMENT=53885653 DEFAULT CHARSET=latin1;

CREATE TABLE `T2` (
  `T2_id` int(11) NOT NULL,
  `T2_val1` float NOT NULL,
  `T2_val2` float NOT NULL,
  `T2_val3` varchar(45) NOT NULL,
  PRIMARY KEY (`T2_id`),
  KEY `T2_val3` (`T2_val3`),
  KEY `T2_val1_2` (`T2_val1`,`T2_val2`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

如您所见,两个表都有一个 AI 主键外键,它们之间匹配为 one-to-one relationshipT1_idT2_id)。我们在T1 上为T1_val4 应用了索引,它采用日期时间格式

选择查询

SELECT 
      T1_val5, 
      T2_val1, 
      T2_val2, 
      T2_val3, 
      T1_val9, 
      count(T2_val1) as cnt, 
      T1_val4
   FROM 
      T1 USE INDEX (T1_val4)
         INNER JOIN T2 
            ON T1.T1_id = T2.T2_id 
   WHERE 
      T1_val4 BETWEEN '2016-02-18 15:00:00' 
                  AND '2016-02-18 16:59:59'
   GROUP BY 
      T2_val1, 
      T2_val2, 
      T2_val3, 
      T1_val9, 
      T1_val5
   order by 
      T1_val4 ASC;

如您所见,我为索引指定了一个提示,以便告诉 MySQL 将该特定索引用于日期时间列。实际上,如果我将 WHERE 条件中的日期时间范围扩展到几个小时。例如BETWEEN '2016-02-18 15:00:00' AND '2016-02-18 23:59:59',执行时间会增长到 50/100 秒。可能我在逻辑上遗漏了一些东西。

EXPLAIN 输出

+-------+---------------+-----------+-----------+-------------------+-----------+---------------+-----------+-----------+------------------------------------------------------------+
|   ID  |   SELECT_TYPE |   TABLE   |   TYPE    |   POSSIBLE_KEYS   |   KEY     |   KEY_LEN     |   REF     |   ROWS    |       EXTRA                                                |
+-------+---------------+-----------+-----------+-------------------+-----------+---------------+-----------+-----------+------------------------------------------------------------+
|   1   |   SIMPLE      |   T1      |   range   |   T1_val4         |   T1_val4 |      5        |   NULL    |   10670   |   "Using index condition; Using temporary; Using filesort" |
+-------+---------------+-----------+-----------+-------------------+-----------+---------------+-----------+-----------+------------------------------------------------------------+
|   1   |   SIMPLE      |   T2      |   eq_ref  |   PRIMARY         |   PRIMARY |      4        |   T1_id   |   1       |     NULL                                                   |
+-------+---------------+-----------+-----------+-------------------+-----------+---------------+-----------+-----------+------------------------------------------------------------+

使用复合索引更新的 EXPLAIN 输出

(根据@O. Jones 的建议)

+-------+---------------+-----------+-----------+------------------------------+-----------+---------------+-----------+-----------+---------------------------------------------------------------+
|   ID  |   SELECT_TYPE |   TABLE   |   TYPE    |   POSSIBLE_KEYS              |   KEY     |   KEY_LEN     |   REF     |   ROWS    |       EXTRA                                                   |
+-------+---------------+-----------+-----------+------------------------------+-----------+---------------+-----------+-----------+---------------------------------------------------------------+
|   1   |   SIMPLE      |   T1      |   range   |   "PRIMARY,ix_rlf"           |   ix_rlf  |      5        |   NULL    |   10906   |   "Using where; Using index; Using temporary; Using filesort" |
+-------+---------------+-----------+-----------+------------------------------+-----------+---------------+-----------+-----------+---------------------------------------------------------------+
|   1   |   SIMPLE      |   T2      |   eq_ref  |   "PRIMARY,ix_cc"            |   PRIMARY |      4        |   T1_id   |   1       |     NULL                                                      |
+-------+---------------+-----------+-----------+------------------------------+-----------+---------------+-----------+-----------+---------------------------------------------------------------+

ix_rlfT1_val4T1_val9T1_val5 的复合索引,ix_cc 是@Tom Shir 为由@ 制成的T2 建议的复合索引987654338@, T2_val1, T2_val2, T2_val3.

查询执行计划可视架构

(将 2 HOURS 视为间隔,在本例中查询结果约为 6632 行,执行时间为 6/7 秒)

【问题讨论】:

  • 请阅读此meta.stackoverflow.com/a/271056 请特别注意查询性能部分。请edit您的问题向我们展示EXPLAIN输出和其他相关信息。顺便说一句,您的GROUP BY 子句还需要提及T1_val4,即您的日期戳列。
  • 你是对的!请给我几分钟,我会添加 EXPLAIN 输出。
  • innodb_buffer_pool_size 的值是多少?你有多少内存?
  • innodb_buffer_pool_size 是 6123683840
  • 请注意,ix_cc 未被选中。无论如何,PRIMARY 几乎一样好。

标签: mysql database performance select query-performance


【解决方案1】:

通过从GROUP BY 子句中省略T1_val4,您正在利用MySQL 的非标准扩展。您可能会得到不想要的结果。请阅读这个。 https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html

通常,将BETWEEN 用于类似日期时间的列是一个坏主意,因为它不能很好地处理范围结束条件。如果我是你,我会写这个

WHERE T1_val4 >= '2016-02-18 15:00:00' AND T1_val4 < '2016-02-18 17:00:00'

您有正确的想法来索引您的日期戳列。您可以尝试使用 compound covering index 而不是简单的日期戳索引。看起来您的查询从T1 中提取了大约一万行,因此复合覆盖索引会有所帮助。将您需要的所有列放入索引中,首先是范围扫描列。这意味着 MySQL 可以通过执行索引范围扫描来满足整个查询,这样更快。索引应该在这些列上。

 T1_val4, T1_val9, T1_val5

因为您使用的是 InnoDB,所以您不必在复合索引中包含主键。

这应该会快一点。但是,您仍然要求 MySQL 检索和索引一万行,这实际上是真正的工作。

【讨论】:

  • 谢谢@O。琼斯为您的答复。我正在阅读我收到的两个答案。你能解释一下为什么只索引T1 应该比索引@Tom Shir 写的T2 更好吗?在性能方面应该略有不同吧?
  • 我已尝试添加您为T1 建议我的复合索引,我已经替换了 BETWEEN 条件,并且我还通过T1_val4 将其包含在组中。但是在这些更改之后,执行性能保持不变。
  • 我不建议索引T2,因为您的EXPLAIN 输出表明您的查询只查看了几行,而且您通过其主键访问它并且没有WHERE条件。添加复合索引后EXPLAIN会告诉你什么?
  • 更好:WHERE T1_val4 &gt;= '2016-02-18 15:00:00' AND T1_val4 &lt; '2016-02-18 15:00:00' + INTERVAL 2 HOUR
  • @UgoL - INTERVAL 模式对性能没有帮助 - 肯定同时发生了其他事情。 (10 倍的加速通常可以追溯到缓存。)
【解决方案2】:

这是带有表前缀的查询:

SELECT
        T1.T1_val5,
        T2.T2_val1,
        T2.T2_val2,
        T2.T2_val3,
        T1.T1_val9,
        COUNT(T2.T2_val1) AS cnt,
        T1.T1_val4 
    FROM
        T1 
    INNER JOIN
        T2.T2 
            ON T1.T1_id = T2.T2_id 
    WHERE
        T1.T1_val4 BETWEEN '2016-02-18 15:00:00' AND '2016-02-18 16:59:59' 
    GROUP BY
        T2.T2_val1,
        T2.T2_val2,
        T2.T2_val3,
        T1.T1_val9,
        T1.T1_val5 
    ORDER BY
        T1.T1_val4 ASC

我相信您可以通过使用正确的索引来提高其性能。 我通过 SQL query optimizer 运行您的查询,我正在使用我自己的查询,建议使用这些索引:

ALTER TABLE `T1` ADD INDEX `T1_index_1` (`T1_id`, `T1_val4`);
ALTER TABLE `T2` ADD INDEX `T2_index_1` (`T2_id`, `T2_val1`, `T2_val2`, `T2_val3`);

另外,请发布说明计划,因为它可以帮助更好地了解 MySQL 当前使用哪些索引。

另一个建议 - 删除您添加的提示。通常,MySQL 会比我们更了解如何优化查询。

【讨论】:

  • fwiw,对于优化T1_val4 上的范围扫描的索引,该列必须在索引的列列表中排在第一位。此外,在 InnoDB 中,主键在前的索引与表本身的结构是冗余的。
  • 你能解释一下为什么范围列应该放在第一位(并且可能包括一个参考链接)吗?如果是这种情况,那么这意味着您不能创建一个索引,其中 ON 子句中的列和范围扫描都将用于优化同一查询?
  • use-the-index-luke.com 是了解查询优化的好资源。 MySQL 索引具有有序的 BTREE 结构。因此,当查询在某个列中调用连续范围的值时,如果该列首先出现,则查询计划器可以随机访问索引到第一个符合条件的条目,然后按顺序扫描到最后一个。
  • 谢谢。我过去浏览过这些文章,发现这个页面指定首先索引相等,然后才索引范围。 use-the-index-luke.com/sql/where-clause/searching-for-ranges/…。等式是 col1=col2 (就像通常发生在 join ON 子句中一样)而不是 col1=const 的情况是否不同?
猜你喜欢
  • 1970-01-01
  • 2012-03-31
  • 1970-01-01
  • 1970-01-01
  • 2019-08-13
  • 2019-10-09
  • 1970-01-01
  • 2021-11-17
  • 2014-03-10
相关资源
最近更新 更多