【问题标题】:using an index when SELECTing from a MySQL join从 MySQL 连接中选择时使用索引
【发布时间】:2017-10-04 01:47:28
【问题描述】:

我有以下两个 MySQL/MariaDB 表:

CREATE TABLE requests (
  request_id      BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  unix_timestamp  DOUBLE NOT NULL,
  [...]
  INDEX unix_timestamp_index (unix_timestamp)
);

CREATE TABLE served_objects (
  request_id      BIGINT UNSIGNED NOT NULL,
  object_name     VARCHAR(255) NOT NULL,
  [...]
  FOREIGN KEY (request_id) REFERENCES requests (request_id)
);

每个表中有几百万列。每个请求有零个或多个服务对象。我有一个通过加入这两个表来提供完整的服务对象视图的视图:

CREATE VIEW served_objects_view AS
SELECT
  r.request_id AS request_id,
  unix_timestamp,
  object_name
FROM requests r
RIGHT JOIN served_objects so ON r.request_id=so.request_id;

到目前为止,这一切似乎都很简单。但是当我像这样做一个简单的选择时:

SELECT * FROM served_objects_view ORDER BY unix_timestamp LIMIT 5;

需要整整一分钟或更长时间。它显然没有使用索引。我尝试了许多不同的方法,包括翻转事物并使用 LEFT 或 INNER 连接,但无济于事。

这是此 SELECT 的 EXPLAIN 的输出:

+------+-------------+-------+--------+---------------+---------+---------+------------------+---------+---------------------------------+
| id   | select_type | table | type   | possible_keys | key     | key_len | ref              | rows    | Extra                           |          
+------+-------------+-------+--------+---------------+---------+---------+------------------+---------+---------------------------------+
|    1 | SIMPLE      | so    | ALL    | NULL          | NULL    | NULL    | NULL             | 5196526 | Using temporary; Using filesort | 
|    1 | SIMPLE      | r     | eq_ref | PRIMARY       | PRIMARY | 8       | db.so.request_id |       1 |                                 |
+------+-------------+-------+--------+---------------+---------+---------+------------------+---------+---------------------------------+

这里有什么基本的东西阻止索引被使用吗?我知道它需要使用临时表来满足视图,并且这会干扰使用索引的能力。但我希望存在一些技巧,允许我从视图中选择,同时尊重请求表中的索引。

【问题讨论】:

  • 尝试添加一个unix_timestamp, object_name 复合索引,这将是一个覆盖索引。

标签: mysql select join indexing


【解决方案1】:

O. Jones 提供的答案是正确的方法;谢谢!这里的大救星是,如果内部 SELECT 仅引用 requests 表中的列(例如仅 SELECTing request_id 的情况),优化器可以在不执行连接的情况下满足视图,使其成为 lickety-split。

不过,我必须进行两项调整,以使其产生与原始 SELECT 相同的结果。首先,如果内部 SELECT 返回非唯一 request_ids,则外部 JOIN 创建这些非唯一条目的叉积。通过将外部 SELECT 更改为 SELECT DISTINCT,可以有效地丢弃这些重复行。

其次,如果 ORDER BY 列可以包含非唯一值,则结果可以包含不相关的行。也可以通过 SELECTing orderByCol 并将 AND a.orderByCol = b.orderByCol 添加到 JOIN 规则来有效地丢弃这些。

因此,如果 orderByCol 来自 requests 表,我的最终解决方案如下:

SELECT DISTINCT a.*
  FROM served_objects_view a
  JOIN (
    SELECT request_id, <orderByCol> FROM served_objects_view
    <whereClause>
    ORDER BY <orderByCol> LIMIT <startRow>,<nRows>
  ) b ON a.request_id = b.request_id AND a.<orderByCol> = b.<orderByCol>
  ORDER BY <orderByCol>;

这是一个比我希望的更复杂的解决方案,但它有效,所以我很高兴。

最后的评论。 INNER JOIN 和 RIGHT JOIN 在这里实际上是相同的东西,所以我最初用 RIGHT JOIN 来表述它,因为这是我概念化它的方式。但是,经过一些实验(在您的挑战之后),我发现 INNER 连接效率更高。 (如果内部 SELECT 仅引用 requests 表中的列,这就是允许优化器在不执行连接的情况下满足视图的原因。)再次感谢!

【讨论】:

    【解决方案2】:

    VIEWs 并不总是得到很好的优化。使用SELECT 时查询是否运行缓慢?您是否添加了建议的索引?

    您使用的是什么版本的 MySQL/MariaDB?较新版本中可能有优化改进,升级可能会有所帮助。

    我的意思是,你可能不得不放弃VIEW

    【讨论】:

      【解决方案3】:

      您正在使用臭名昭著的性能反模式。

       SELECT * FROM served_objects_view ORDER BY unix_timestamp LIMIT 5;
      

      您已告诉查询规划器制作整个视图的副本(在 RAM 或临时存储中),对其进行排序,然后丢弃除五行之外的所有视图。所以,它服从了。它真的不在乎花了多长时间。

      SELECT * 通常被认为对查询性能有害,这就是这种情况的原因。

      试试这个延迟连接优化

      SELECT a.* 
        FROM served_objects_view a
        JOIN (
               SELECT request_id
                 FROM served_objects_view 
                ORDER BY unix_timestamp
                LIMIT 5
              ) b ON a.request_id = b.request_id
      

      这会对较小的数据子集(仅 request_id 和时间戳值)进行排序。然后它获取视图行的一小部分。

      如果它对于您的目的仍然太慢,请尝试在 request (unix_timestamp, request_id) 上创建一个复合索引。但这可能是不必要的。如果有必要,集中精力优化子查询。

      备注:RIGHT JOIN?真的吗?你不想要JOIN吗?

      【讨论】:

        猜你喜欢
        • 2014-05-16
        • 2023-03-09
        • 1970-01-01
        • 2018-11-18
        • 1970-01-01
        • 2021-12-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多