使MySQL为查询选择最佳索引

Vla*_*tow 4 mysql sql database indexing performance

在MySQL 5.6 DB中,我具有以下结构的巨大SQL表:

CREATE TABLE `tbl_requests` (
    `request_id` BIGINT(20) UNSIGNED NOT NULL,
    `option_id` BIGINT(20) UNSIGNED NOT NULL,
    `symbol` VARCHAR(30) NOT NULL,
    `request_time` DATETIME(6) NOT NULL,
    `request_type` SMALLINT(6) NOT NULL,
    `count` INT(11) NOT NULL,
    PRIMARY KEY (`request_id`),
    INDEX `key_request_type_symbol` (`request_type`, `symbol`),
    INDEX `key_request_time` (`request_time`),
    INDEX `key_request_symbol` (`symbol`)
);
Run Code Online (Sandbox Code Playgroud)

该表中有超过8亿条记录,其中约有25,000个symbol字段种类,其中约有100个不同的值request_type。我的目标是使查询尽可能快:

SELECT tbl_requests.*
FROM tbl_requests  use index (key_request_type_symbol)
-- use index (key_request_time) -- use index (key_request_type_symbol)
WHERE (tbl_requests.request_time >= '2016-02-23' AND 
       tbl_requests.request_time <= '2016-12-23') 
AND (tbl_requests.request_type IN (0, 1, 9))  
[AND (tbl_requests.symbol = 'AAPL' ... )]
ORDER BY tbl_requests.request_time DESC, tbl_requests.request_id DESC
LIMIT 0,100;
Run Code Online (Sandbox Code Playgroud)

从无过滤tbl_requests.symbol器到一组值,再到一组匹配模式到混合和匹配,按字段进行不同类型的过滤。

我看到的是,在不同情况下,不同的索引可以提供最佳性能,而MySQL无法猜测哪个索引会更好。例如,在没有过滤器的情况下,最快的是key_request_time索引(0.016秒),MySQL会正确选择它(EXPLAIN命令的结果):

"id": 1,
"select_type": "SIMPLE",
"table": "tbl_requests",
"type": "range",
"possible_keys": "key_request_type_symbol,key_request_time",
"key": "key_request_time",
"key_len": "8",
"ref": null,
"rows": 428944675,
"Extra": "Using index condition; Using where"
Run Code Online (Sandbox Code Playgroud)

如果使用索引key_request_type_symbol索引,则此查询将花费大量时间(也许数小时?)。

我使用语法

FROM tbl_requests use index (key_request_type_symbol)
Run Code Online (Sandbox Code Playgroud)

强制使用索引。

当过滤器中使用一个符号时

AND (tbl_requests.symbol = 'BAC')
Run Code Online (Sandbox Code Playgroud)

MySQL服务器正在选择相同的key_request_time索引,查询耗时超过10秒。但是,如果使用key_request_type_symbol索引,则查询大约需要0.7秒。同样,当使用第一个索引时,如果再次重复查询,它将持续超过10秒;而当使用第二个索引时,重复查询将花费0.1秒。索引说明
信息key_request_type_symbol

"id": 1,
"select_type": "SIMPLE",
"table": "tbl_requests",
"type": "range",
"possible_keys": "key_request_type_symbol",
"key": "key_request_type_symbol",
"key_len": "34",
"ref": null,
"rows": 17117,
"Extra": "Using index condition; Using where; Using filesort"
Run Code Online (Sandbox Code Playgroud)

行少得多,但具有文件排序功能。

看起来key_request_type_symbol很重要,表中有多少匹配的行。对于“ AMZN”符号,行= 79762,时间为0.15秒,而使用key_request_time索引则需要4.4秒。但是MySQL更喜欢它key_request_type_symbol

在下面的示例中可以清楚地看到。如果我使用:

tbl_requests.symbol LIKE 'A%' 
Run Code Online (Sandbox Code Playgroud)

key_request_time索引需要0.172秒。
使用key_request_type_symbol索引需要173秒。(慢1000倍)
行= 6367732

对于:

tbl_requests.symbol LIKE 'AM%' 
Run Code Online (Sandbox Code Playgroud)

key_request_time索引需要0.640秒。
key_request_type_symbol索引它需要2.2秒。(慢3倍)
行= 838822

对于:

tbl_requests.symbol LIKE 'AMZ%' 
Run Code Online (Sandbox Code Playgroud)

key_request_time索引它需要4.5秒。
key_request_type_symbol索引花费0.15秒。(快30倍)
行= 73083

对于:

tbl_requests.symbol LIKE 'AMZN%' 
Run Code Online (Sandbox Code Playgroud)

使用key_request_time索引需要4.4秒。
key_request_type_symbol索引花费0.15秒。(快30倍)
行= 79762

同样,当使用key_request_type_symbol索引时,如果再次使用相同的符号过滤器,则执行速度将大大提高,而key_request_time时序却保持不变。

我将用一个符号接收很多查询,所以我需要它们要快。但是我也可能收到由许多符号过滤的查询。在每种情况下,如何强制服务器为我选择最快的方式?

我可以想象的一种方法是在key_request_type_symbol索引之前发送EXPLAIN语句并检查期望的行数,然后修改查询以相应地使用此索引或该索引(例如,如果行超过300000,请使用key_request_time)。

但是也许我缺少什么?也许索引不正确(但我找不到更好的索引)?最好保持查询不变,并强制MySQL足够智能以自动选择最快的方式。

Bil*_*win 8

这是关于MySQL如何使用索引的缺失规则:

  1. 索引中最左边的列必须与相等性比较的列(例如symbol = 'AAPL')匹配。您可以有几列,只要它们都满足相等条件即可。
  2. 然后,索引中的下一下一列可以匹配一列以进行范围比较。范围比较不等于相等。所以:<>><IN()BETWEENLIKE没有领先的通配符,或IS [NOT] NULL
  3. 索引也可以用于GROUP BYORDER BY,但是如果您将索引用于范围条件则不能使用。基本上,您在索引中再进行一列,然后再进行相等性测试。

示例:假设您具有以下条件的查询:

WHERE a = 1 AND b = 2 AND c > 3 AND d IN (4,5,6)
Run Code Online (Sandbox Code Playgroud)

假设您按此顺序在(a,b,c,d)上有一个索引。只有索引中的a,b,c列才可以帮助查询。由于c列处于不等式比较中,因此这是索引中的最后一列。

(实际上,InnoDB最近具有一项称为“索引条件下推”的功能,该功能可以使存储引擎通过搜索d的值提供更多帮助,但不要指望它像常规索引查找那样好。我看到了笔记在你的EXPLAIN输出中的一个“使用索引的条件”,这表明它的使用这一功能。阅读http://dev.mysql.com/doc/refman/5.7/en/index-condition-pushdown-optimization.html为更多细节。)

同样,由于c的不等式条件,该查询将不能使用d来避免后续查询中的文件排序。

WHERE a = 1 AND b = 2 AND c > 3
ORDER BY d
Run Code Online (Sandbox Code Playgroud)

而以下内容将能够使用d来优化排序,因为一旦查询找到了c = 3的行的子集,则其余匹配项自然会以d顺序读取。

WHERE a = 1 AND b = 2 AND c = 3
ORDER BY d
Run Code Online (Sandbox Code Playgroud)

现在,这适用于您的查询:

WHERE (tbl_requests.request_time >= '2016-02-23' AND 
       tbl_requests.request_time <= '2016-12-23') 
AND (tbl_requests.request_type IN (0, 1, 9))  
[AND (tbl_requests.symbol = 'AAPL' ... )]
ORDER BY tbl_requests.request_time DESC, tbl_requests.request_id DESC
Run Code Online (Sandbox Code Playgroud)

符号上的条件是相等。那应该在索引的最左边。

request_time和request_type的条件都是不相等的。您只能从索引中的一个受益。选择最有选择性的搜索范围-缩小搜索范围。以防ICP有所帮助,将另一列添加到索引中。

我想在大多数情况下request_time列的选择性更高。我发现您的病情是10个月的范围,可能是您餐桌上的大部分时间,但是根据您选择的日期范围,范围可能会更窄。

同样,request_type的三个值0、1、9也可能与表中的大多数行匹配。如果是这样,那么该条件将不会具有很高的选择性,因此我将该列放在最后。

ALTER TABLE tbl_requests ADD INDEX (symbol, request_time, request_type);
Run Code Online (Sandbox Code Playgroud)

要求request_time的顺序在不等式条件之后发生,因此无法避免对匹配的行进行文件排序。