2017-05-08 79 views
3

MySQL版本5.7.18的问题。早期版本的MySQL的行为应该如此。与JOIN不使用索引的MySQL

这里有两张表。表1:

CREATE TABLE `test_events` (
    `id` int(11) NOT NULL, 
    `event` int(11) DEFAULT '0', 
    `manager` int(11) DEFAULT '0', 
    `base_id` int(11) DEFAULT '0', 
    `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 
    `client` int(11) DEFAULT '0', 
    `event_time` datetime DEFAULT '0000-00-00 00:00:00' 
) ENGINE=InnoDB DEFAULT CHARSET=utf8; 


ALTER TABLE `test_events` 
    ADD PRIMARY KEY (`id`), 
    ADD KEY `client` (`client`), 
    ADD KEY `event_time` (`event_time`), 
    ADD KEY `manager` (`manager`), 
    ADD KEY `base_id` (`base_id`), 
    ADD KEY `create_time` (`create_time`); 

,第二个表:

CREATE TABLE `test_event_types` (
    `id` int(11) NOT NULL, 
    `name` varchar(255) DEFAULT NULL, 
    `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 
    `base` varchar(255) DEFAULT NULL 
) ENGINE=InnoDB DEFAULT CHARSET=utf8; 

ALTER TABLE `test_event_types` 
    ADD PRIMARY KEY (`id`); 

让我们尝试选择从基地 “314” 最后事件:

EXPLAIN SELECT `test_events`.`create_time` 
    FROM `test_events` 
    LEFT JOIN `test_event_types` 
      ON (`test_events`.`event` = `test_event_types`.`id`) 
    WHERE base = 314 
    ORDER BY `test_events`.`create_time` DESC 
    LIMIT 1; 
 
+----+-------------+------------------+------------+------+---------------+------+---------+------+--------+----------+----------------------------------------------------+ 
| id | select_type | table   | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra            | 
+----+-------------+------------------+------------+------+---------------+------+---------+------+--------+----------+----------------------------------------------------+ 
| 1 | SIMPLE  | test_events  | NULL  | ALL | NULL   | NULL | NULL | NULL | 434928 | 100.00 | Using temporary; Using filesort     | 
| 1 | SIMPLE  | test_event_types | NULL  | ALL | PRIMARY  | NULL | NULL | NULL |  44 |  2.27 | Using where; Using join buffer (Block Nested Loop) | 
+----+-------------+------------------+------------+------+---------------+------+---------+------+--------+----------+----------------------------------------------------+ 
2 rows in set, 1 warning (0.00 sec) 

MySQL是不使用索引和读取整个表格。 没有WHERE语句:

EXPLAIN SELECT `test_events`.`create_time` 
    FROM `test_events` 
    LEFT JOIN `test_event_types` 
      ON (`test_events`.`event` = `test_event_types`.`id`) 
    ORDER BY `test_events`.`create_time` DESC 
    LIMIT 1; 
 
+----+-------------+------------------+------------+--------+---------------+-------------+---------+-----------------------+------+----------+-------------+ 
| id | select_type | table   | partitions | type | possible_keys | key   | key_len | ref     | rows | filtered | Extra  | 
+----+-------------+------------------+------------+--------+---------------+-------------+---------+-----------------------+------+----------+-------------+ 
| 1 | SIMPLE  | test_events  | NULL  | index | NULL   | create_time | 4  | NULL     | 1 | 100.00 | NULL  | 
| 1 | SIMPLE  | test_event_types | NULL  | eq_ref | PRIMARY  | PRIMARY  | 4  | m16.test_events.event | 1 | 100.00 | Using index | 
+----+-------------+------------------+------------+--------+---------------+-------------+---------+-----------------------+------+----------+-------------+ 
2 rows in set, 1 warning (0.00 sec) 

现在它使用索引。

MySQL 5.5.55在这两种情况下都使用索引。为什么是这样以及如何处理它?

回答

2

我不知道你在以前和当前的安装中看到的不同,但服务器的行为是有道理的。

SELECT test_events.create_time FROM test_events LEFT JOIN test_event_types ON ( test_events.event = test_event_types.id) ORDER BY test_events.create_time DESC LIMIT 1; 

在这个查询中,您没有where子句,但是只提取一行。这是排序create_time后,恰好有一个索引。并且该索引可以用于排序。但让我们看看第二个查询。

SELECT test_events.create_time FROM test_events LEFT JOIN test_event_types ON ( test_events.event = test_event_types.id) WHERE base = 314 ORDER BY test_events.create_time DESC LIMIT 1 

您在基准列上没有索引。所以没有索引可以用于此。要查找相关记录,mysql必须执行表扫描。确定相关行后,需要对其进行排序。但是,在这种情况下,查询规划已经决定,它只是不值得使用索引上create_time

我看到几个问题你的设置,对base如已经提到的第一是没有和索引。但为什么base varchar?你似乎在存储整数。

ALTER TABLE test_events 
    ADD PRIMARY KEY (id), 
    ADD KEY client (client), 
    ADD KEY event_time (event_time), 
    ADD KEY manager (manager), 
    ADD KEY base_id (base_id), 
    ADD KEY create_time (create_time); 

而使得像这样的多个索引在mysql中没有多大意义。这是因为mysql只能为每个表使用一个索引进行查询。用一个或两个指标你会好得多。可能是多列索引。

我觉得你的理想指数将同时包含CREATE_TIME和事件字段

+0

这很奇怪,但它的工作原理。如果我将列“base”的类型更改为整数并向此列添加索引,MySQL将开始在“create_time”字段中使用索引。如果我只添加索引或只将类型更改为int - 它的行为与以前一样。前一段时间错误地将Varchar设置为字段“base”,但在更新到最后一个版本的MySQL之前它已经正常。谢谢。 –

+0

我不希望在表格中使用不会超过5-10行的索引(主索引除外)。但我认为这是一个错误的策略。但是,问题仍然存在于其他JOIN到文本字段中。在上面的示例中,我可以轻松地将varchar更改为整数,但是我无法在其他表中使用该字段,其中该字段包含一些文本代码。 –

+0

我不知道这个表有少于10行。那么当然不需要索引。但后来mysql查询计划一直很奇怪 – e4c5

1

base = 314base VARCHAR...是一个性能问题。要么将报价放在314左右,要么将base设置为某种整数类型。

您似乎不需要LEFT。如果没有,那么做一个普通的JOIN,这样优化器可以自由地开始INDEX(base),然后丢失和需要。

至于5.5和5.6和5.7之间的差异,出现了一些优化变化;你可能遇到了回归。但是我不想在查询和索引得到改进之前追逐它。

+0

我可以用引号和LEFT去掉,但结果是一样的。 MySQL 5.7在应该使用的地方不使用索引。假设它不是314,而是“z314” –

+0

@TarasBulgakov - 使用'z314',您将不得不使用引号。 –