从MySQL连接中选择时使用索引

时间:2017-05-05 14:17:50

标签: mysql select join indexing

我有以下两个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)
);

每张表中有数百万列。每个请求有零个或多个serving_objects。我有一个视图,通过连接这两个表提供完整的serve_objects视图:

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:

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

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

3 个答案:

答案 0 :(得分:2)

您正在使用臭名昭着的反对模式。

main()

您已经告诉查询规划人员复制整个视图(在RAM或临时存储中),对其进行排序,并抛出除五行之外的所有视图。所以,它服从了。它真的没关心它需要多长时间。

import csv import os import PyPDF2 os.chdir('C:/Users/User/Desktop') def getText(happy_file): pdf_file_obj = open(happy_file, 'rb') pdf_reader = PyPDF2.PdfFileReader(pdf_file_obj) pdf_reader.numPages #optional page_obj = pdf_reader.getPage(0) return page_obj.extractText() def writeToCSV(happy_file): output_file = open('myfinalfile.csv', 'w', newline ='') output_writer = csv.writer(output_file) output_writer.writerow([str(getText(happy_file))]) output_file.close() def main(): writeToCSV("anyfile.pdf") if __name__ == "__main__": main() 通常被认为对查询效果有害,这就是为什么这是真的。

尝试此延迟加入优化

 SELECT * FROM served_objects_view ORDER BY unix_timestamp LIMIT 5;

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

如果它仍然太慢,请尝试在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 (unix_timestamp, request_id)吗?

答案 1 :(得分:0)

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

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

我的观点是,你可能不得不放弃VIEW

答案 2 :(得分:0)

O. Jones提供的答案是正确的方法;谢谢!这里的大救星是,如果内部SELECT仅引用来自请求表的列(例如,仅选择request_id时的情况),优化器可以在不执行连接的情况下满足视图,使其变得非常分裂。

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

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

所以我的最终解决方案,如果orderByCol来自请求表,效果很好,如下:

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仅引用请求表中的列,那么优化器可以在不执行连接的情况下满足视图。)再次感谢!