【问题标题】:Mysql Derived Table PerformanceMysql 派生表性能
【发布时间】:2017-04-30 01:13:21
【问题描述】:

我有一个包含以下列的表格:

record_id

source_id

user_id

移动

调用_at

我正在尝试运行这两个查询

SELECT
      t1.user_id,
      t1.mobile,
      COUNT(DISTINCT(t1.called_at )) AS cnt
    FROM
      (
        SELECT
          user_id,
          mobile,
          called_at
        FROM
          users
        WHERE
          called_at >= "2016-09-01" AND called_at < "2016-12-01" and user_id is NOT NULL
      ) t1
    GROUP BY t1.user_id, t1.mobile
    HAVING cnt > 1

SELECT
      user_id,
      mobile,
      COUNT(DISTINCT(called_at )) AS cnt
      FROM users
      WHERE called_at >= "2016-09-01" AND called_at < "2016-12-01" and user_id is NOT NULL
    GROUP BY user_id, mobile
    HAVING cnt > 1

两个查询在逻辑上是相同的,并且也给出相同的输出。但是第一个查询运行得非常快~ 3 秒,第二个~ 55 秒。

甚至解释说第一个查询涉及使用文件排序对派生表进行额外扫描,但速度仍然快得多。

这怎么可能?

解释输出:

+----+-------------+-----------------------+------+-----------------------+------+---------+------+---------+----------------+
| id | select_type | table                 | type | possible_keys         | key  | key_len | ref  | rows    | Extra          |
+----+-------------+-----------------------+------+-----------------------+------+---------+------+---------+----------------+
|  1 | PRIMARY     | <derived2>            | ALL  | NULL                  | NULL | NULL    | NULL | 1025150 | Using filesort |
|  2 | DERIVED     | users                 | ALL  | idx_fa_af,idx_a_di_um | NULL | NULL    | NULL | 2221923 | Using where    |
+----+-------------+-----------------------+------+-----------------------+------+---------+------+---------+----------------+

+----+-------------+-----------------------+-------+-----------------------+-------------+---------+------+---------+-------------+
| id | select_type | table                 | type  | possible_keys         | key         | key_len | ref  | rows    | Extra       |
+----+-------------+-----------------------+-------+-----------------------+-------------+---------+------+---------+-------------+
|  1 | SIMPLE      | users                 | index | idx_fa_af,idx_a_di_um | idx_a_di_um | 23      | NULL | 2221923 | Using where |
+----+-------------+-----------------------+-------+-----------------------+-------------+---------+------+---------+-------------+

| users | CREATE TABLE `users` (
  `record_id` varchar(100) NOT NULL,
  `source_id` int(11) NOT NULL,
  `user_id` int(11) DEFAULT NULL,
  `mobile` varchar(15) DEFAULT NULL,
  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `called_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00'
  UNIQUE KEY `idx_unique_a_ri_si` (`record_id`,`source_id`),
  KEY `idx_fa_af` (`called_at`),
  KEY `idx_fa_um` (`mobile`),
  KEY `idx_a_di_um` (`user_id`,`mobile`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 |


+----------------------------+---------+
| Variable_name              | Value   |
+----------------------------+---------+
| Handler_commit             | 1       |
| Handler_delete             | 0       |
| Handler_discover           | 0       |
| Handler_external_lock      | 2       |
| Handler_mrr_init           | 0       |
| Handler_prepare            | 0       |
| Handler_read_first         | 1       |
| Handler_read_key           | 1       |
| Handler_read_last          | 0       |
| Handler_read_next          | 0       |
| Handler_read_prev          | 0       |
| Handler_read_rnd           | 0       |
| Handler_read_rnd_next      | 3676447 |
| Handler_rollback           | 0       |
| Handler_savepoint          | 0       |
| Handler_savepoint_rollback | 0       |
| Handler_update             | 0       |
| Handler_write              | 1208173 |
+----------------------------+---------+

+----------------------------+---------+
| Variable_name              | Value   |
+----------------------------+---------+
| Handler_commit             | 1       |
| Handler_delete             | 0       |
| Handler_discover           | 0       |
| Handler_external_lock      | 2       |
| Handler_mrr_init           | 0       |
| Handler_prepare            | 0       |
| Handler_read_first         | 1       |
| Handler_read_key           | 1       |
| Handler_read_last          | 0       |
| Handler_read_next          | 2468272 |
| Handler_read_prev          | 0       |
| Handler_read_rnd           | 0       |
| Handler_read_rnd_next      | 0       |
| Handler_rollback           | 0       |
| Handler_savepoint          | 0       |
| Handler_savepoint_rollback | 0       |
| Handler_update             | 0       |
| Handler_write              | 0       |
+----------------------------+---------+

【问题讨论】:

  • 也发布 EXPLAIN 输出
  • 包括 EXPLAIN 的输出。
  • 你也可以发布SHOW CREATE TABLE users的输出吗?
  • 更新了描述。

标签: mysql performance derived-table


【解决方案1】:

添加INDEX(user_id, called_at, mobile),然后将每个查询运行两次。两次是为了避免可能隐藏 I/O 的缓存问题。

怀疑第一个查询运行得很快,因为它都在 RAM 中。第二个是使用未缓存的索引idx_a_di_um

我建议的索引应该使它们都运行得更快。

列的任何组合是否“唯一”?如果是这样,请将组合设为PRIMARY KEY。这将进一步改善情况。如果没有,至少提供一个id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY

为什么会有帮助

索引是 BTree。 (有关好的定义,请参阅 wikipedia。)该索引结构与数据分开,数据位于单独的 BTree 中,由PRIMARY KEY 排序。 BTree 在查找一行或一组连续行方面非常有效。 (根据索引“连续”。)使用辅助键时(即不是PRIMARY),首先定位索引的行,然后查找每个数据行使用PRIMARY KEY。除非...如果 所有 SELECT 中所需的列都在辅助键中,则无需访问数据。这称为“覆盖”; EXPLAIN 通过说“使用索引”来表示它。我的索引是子查询的“覆盖”索引。

任何索引中列的顺序都很重要。在这种情况下,索引将所有 user_id IS NOT NULL 行放在一起。但这是关于 3 列顺序的唯一论据。

处理程序技巧

这是一种更深入地了解查询正在做什么的方法,它不依赖于缓存、服务器重启等:

FLUSH STATUS;
SELECT ...;
SHOW SESSION STATUS LIKE 'Handler%';

看起来像表大小(行)的数字表示表(或索引)扫描。看起来像输出大小的数字表示一些最终操作。 Handler_write... 表示一个 tmp 表。等等。

【讨论】:

  • 嘿,我什至尝试通过重新启动服务器来运行这两个查询。还是一样的表现。在我添加了你提到的索引之后,现在两者都运行得更快了。第一个耗时 2.5 秒,第二个耗时 3.6 秒。仍然无法解释为什么第一个更快。已更新描述以添加唯一键。您能否解释一下该索引如何帮助加快处理速度?
  • 重启服务器会给你带来最差的 I/O 时间——通常是不现实的,尤其是对于持续运行的生产服务器。
  • 嗯? record_idsource_id 未列在 SHOW CREATE TABLE??
  • 我添加了一种获得更多洞察力的技术。考虑在两个查询上运行它并显示结果。 (至少是非零行。)也许这会给我们一个线索,为什么第一个更快。
  • 添加了“为什么会有帮助”。
猜你喜欢
  • 2012-04-23
  • 1970-01-01
  • 2017-09-09
  • 2011-01-20
  • 2010-09-23
  • 2012-02-24
  • 1970-01-01
  • 1970-01-01
  • 2015-01-26
相关资源
最近更新 更多