Mysql 派生表性能

标签 mysql performance derived-table

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

记录ID

来源 ID

用户 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       |
+----------------------------+---------+

最佳答案

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

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

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

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

为什么会有帮助

索引是一个 BTree。 (有关详细定义,请参阅维基百科。)该索引结构与数据分开,数据位于单独的 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...表示临时表。等等

关于Mysql 派生表性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41147136/

相关文章:

mysql,如何使 'insert on duplicate key update'存储上一个。值(value)观?

安卓代码增强

R 重命名一个对象/data.frame 没有中间对象

Mysql 引用嵌套查询的派生表

mysql - MySQL 上的子查询

mysql - 如何选择表中的最新数据?

mysql - rails 和 RSpec : test DB with preexisting records

c# - 当 exe 位于网络上时,我的 C#.NET 应用程序运行速度较慢

sql-server-2005 - SQL左联接(多个联接条件)

mysql - 错误代码: 1248. Every derived table must have its own alias 找不到查询的解决方案