复合索引最左列中的通配符是不是意味着索引中的剩余列不用于索引查找(MySQL)?

Posted

技术标签:

【中文标题】复合索引最左列中的通配符是不是意味着索引中的剩余列不用于索引查找(MySQL)?【英文标题】:Does wildcard in left-most column of composite index mean remaining columns in index aren't used in index lookup (MySQL)?复合索引最左列中的通配符是否意味着索引中的剩余列不用于索引查找(MySQL)? 【发布时间】:2015-11-29 00:36:52 【问题描述】:

假设您有一个主要的复合索引last_name,first_name。然后你搜索了WHERE first_name LIKE 'joh%' AND last_name LIKE 'smi%'

last_name 条件中使用的通配符是否意味着在进一步帮助 mysql 查找索引时不会使用 first_name 条件?换句话说,通过在 last_name 条件上放置通配符,MySQL 只会进行部分索引查找(并忽略 last_name 右侧的列中给出的条件)?

进一步澄清我的问题

示例 1:主键是 last_name, first_name。 示例 2:主键是 last_name

使用这个 WHERE 子句:WHERE first_name LIKE 'joh%' AND last_name LIKE 'smi%',Example-1 会比 Example-2 快吗?

更新

这是一个 sqlfiddle: http://sqlfiddle.com/#!9/6e0154/3

CREATE TABLE `people1` (
    `id` INT(11),
    `first_name` VARCHAR(255) NOT NULL,
    `middle_name` VARCHAR(255) NOT NULL,
    `last_name` VARCHAR(255) NOT NULL,
    PRIMARY KEY (`id`),
    INDEX `name` (`last_name`(15), `first_name`(10))
  )
COLLATE='latin1_swedish_ci'
ENGINE=InnoDB;

CREATE TABLE `people2` (
    `id` INT(11),
    `first_name` VARCHAR(255) NOT NULL,
    `middle_name` VARCHAR(255) NOT NULL,
    `last_name` VARCHAR(255) NOT NULL,
    PRIMARY KEY (`id`),
    INDEX `name` (`last_name`(15))
  )
COLLATE='latin1_swedish_ci'
ENGINE=InnoDB;

INSERT INTO `people1` VALUES
(1,'John','','Smith'),(2,'Joe','','Smith'),(3,'Tom','','Smith'),(4,'George','','Washington');
INSERT INTO `people2` VALUES
(1,'John','','Smith'),(2,'Joe','','Smith'),(3,'Tom','','Smith'),(4,'George','','Washington');

# Query 1A
EXPLAIN SELECT * FROM `people1` WHERE `first_name` LIKE 'joh%' AND `last_name` LIKE 'smi%';
# Query 1B
EXPLAIN SELECT * FROM `people1` WHERE `first_name` LIKE 'joh%' AND `last_name` LIKE 'john';

# Query 2A
EXPLAIN SELECT * FROM `people2` WHERE `first_name` LIKE 'joh%' AND `last_name` LIKE 'smi%';
# Query 2B
EXPLAIN SELECT * FROM `people2` WHERE `first_name` LIKE 'joh%' AND `last_name` LIKE 'john';

【问题讨论】:

我不确定,但我知道 WHERE 子句中的比较顺序无关紧要,就像在等效的数学运算中一样。所以我的猜测是它会以任何一种方式使用复合索引。但是,您可以使用EXPLAIN 了解它的实际作用。 是的,WHERE 子句中这些条件的顺序无关紧要(就像数学一样)。但这不是我的问题。我发布了完整的查询,然后询问first_name LIKE 'smi%' 是否有助于索引搜索。我认为这让人们感到困惑,很抱歉,更新了问题。 :-) 【参考方案1】:

这是您的问题。复数。通过改写它们(用“换句话说”),它们只是不同的问题。这样做并不一定会使响应者更容易。恰恰相反。

Q1:【题目问题】复合索引最左列的通配符是否意味着索引中的剩余列不用于索引查找(MySQL)?

A1:不,不是那个意思。


Q2:last_name 条件中使用的通配符是否意味着在进一步帮助 MySQL 查找索引时不会使用 first_name 条件?

A2:不,不是那个意思。加上那个问题的尾巴是模棱两可的。它已经知道使用什么索引可能是对这种模糊性的一个分支答案。


Q3:换句话说,通过在 last_name 条件上放置通配符,MySQL 将只进行部分索引查找(并忽略 last_name 右侧列中给出的条件)?

A3:不。最右边的列是从索引中提供的,类似于覆盖索引策略,受益于数据页面查找的缓慢性。


Q4:...Example-1 会比 Example-2 快吗?

A4:是的。它是关于这些列的覆盖索引。请参阅覆盖索引。

顺便说一句,关于第四季度。它是 PK 还是非 PK 无关紧要。可能有十几个原因导致 PK 对您的应用程序不利。


原始答案如下:

只有(last_name,first_name) 上的复合键 和你提到的一个查询

WHERE first_name LIKE 'joh%'

...它根本不会使用索引。它将进行表扫描。由于没有

first_name 上的单列键 带有first_name 的复合键最左边

所以我们来了表扫描。

请参阅手册页Multiple-Column Indexes 了解更多信息。并专注于它的left-most 概念。事实上,去那个页面,搜索left这个词。

请参阅 mysql 中 Explain 工具的手册页。还有文章Using Explain to Write Better Mysql Queries。


编辑

自从我一两个小时前来到这里以来,对这个问题进行了一些修改。我将为您留下以下内容。通过解释运行您的实际查询,并通过上面的Using Explain ... 链接或其他参考来破译

drop table myNames;
create table myNames
(   id int auto_increment primary key,
    lastname varchar(100) not null,
    firstname varchar(100) not null,
    col4 int not null,
    key(lastname,firstname)
);
truncate table myNames;
insert myNames (lastName,firstName,col4) values
('Smith','John',1),('Smithers','JohnSomeone',1),('Smith3','John4324',1),('Smi','Jonathan',1),('Smith123x$FA','Joh',1),('Smi3jfif','jkdid',1),('r3','fe2',1);

insert myNames (lastName,firstName,col4) select lastname,firstname,col4 from mynames;
insert myNames (lastName,firstName,col4) select lastname,firstname,col4 from mynames;
insert myNames (lastName,firstName,col4) select lastname,firstname,col4 from mynames;
insert myNames (lastName,firstName,col4) select lastname,firstname,col4 from mynames;
insert myNames (lastName,firstName,col4) select lastname,firstname,col4 from mynames;
insert myNames (lastName,firstName,col4) select lastname,firstname,col4 from mynames;
insert myNames (lastName,firstName,col4) select lastname,firstname,col4 from mynames;
insert myNames (lastName,firstName,col4) select lastname,firstname,col4 from mynames;
insert myNames (lastName,firstName,col4) select lastname,firstname,col4 from mynames;
insert myNames (lastName,firstName,col4) select lastname,firstname,col4 from mynames;
insert myNames (lastName,firstName,col4) select lastname,firstname,col4 from mynames;
insert myNames (lastName,firstName,col4) select lastname,firstname,col4 from mynames;
insert myNames (lastName,firstName,col4) select lastname,firstname,col4 from mynames;
insert myNames (lastName,firstName,col4) select lastname,firstname,col4 from mynames;
insert myNames (lastName,firstName,col4) select lastname,firstname,col4 from mynames;
insert myNames (lastName,firstName,col4) select lastname,firstname,col4 from mynames;

select count(*) from myNames; 
-- 458k rows

select count(*)
from myNames
where lastname like 'smi%';
-- 393216 rows

select count(*)
from myNames
where lastname like 'smi%' and firstname like 'joh%';
-- 262144 rows

Explainrows 呈现巫毒数字。巫毒?是的,因为可能会运行一个小时的查询,所以您要求explain 给您一个模糊计数,而不是运行它,并在 2 秒或更短的时间内给您答案。如果没有explain,当它真正运行时,不要认为这些是真正的计数#。

explain 
select count(*) 
from myNames 
where lastname like 'smi%';
+----+-------------+---------+-------+---------------+----------+---------+------+--------+--------------------------+
| id | select_type | table   | type  | possible_keys | key      | key_len | ref  | rows   | Extra                    |
+----+-------------+---------+-------+---------------+----------+---------+------+--------+--------------------------+
|  1 | SIMPLE      | myNames | range | lastname      | lastname | 302     | NULL | 233627 | Using where; Using index |
+----+-------------+---------+-------+---------------+----------+---------+------+--------+--------------------------+

explain 
select count(*) 
from myNames 
where lastname like 'smi%' and firstname like 'joh%' and col4=1;
+----+-------------+---------+-------+---------------+----------+---------+------+--------+--------------------------+
| id | select_type | table   | type  | possible_keys | key      | key_len | ref  | rows   | Extra                    |
+----+-------------+---------+-------+---------------+----------+---------+------+--------+--------------------------+
|  1 | SIMPLE      | myNames | range | lastname      | lastname | 604     | NULL | 233627 | Using where; Using index |
+----+-------------+---------+-------+---------------+----------+---------+------+--------+--------------------------+


-- the below chunk is interest. Look at the Extra column

explain 
select count(*) 
from myNames 
where lastname like 'smi%' and firstname like 'joh%' and col4=1;
+----+-------------+---------+------+---------------+------+---------+------+--------+-------------+
| id | select_type | table   | type | possible_keys | key  | key_len | ref  | rows   | Extra       |
+----+-------------+---------+------+---------------+------+---------+------+--------+-------------+
|  1 | SIMPLE      | myNames | ALL  | lastname      | NULL | NULL    | NULL | 457932 | Using where |
+----+-------------+---------+------+---------------+------+---------+------+--------+-------------+

explain 
select count(*) 
from myNames 
where firstname like 'joh%';
+----+-------------+---------+-------+---------------+----------+---------+------+--------+--------------------------+
| id | select_type | table   | type  | possible_keys | key      | key_len | ref  | rows   | Extra                    |
+----+-------------+---------+-------+---------------+----------+---------+------+--------+--------------------------+
|  1 | SIMPLE      | myNames | index | NULL          | lastname | 604     | NULL | 453601 | Using where; Using index |
+----+-------------+---------+-------+---------------+----------+---------+------+--------+--------------------------+


analyze table myNames;
+----------------------+---------+----------+----------+
| Table                | Op      | Msg_type | Msg_text |
+----------------------+---------+----------+----------+
| so_gibberish.mynames | analyze | status   | OK       |
+----------------------+---------+----------+----------+

select count(*) 
from myNames where left(lastname,3)='smi';
-- 393216 -- the REAL #
select count(*) 
from myNames where left(lastname,3)='smi' and left(firstname,3)='joh';
-- 262144 -- the REAL #

explain 
select lastname,firstname 
from myNames  
where lastname like 'smi%' and firstname like 'joh%';
+----+-------------+---------+-------+---------------+----------+---------+------+--------+--------------------------+
| id | select_type | table   | type  | possible_keys | key      | key_len | ref  | rows   | Extra                    |
+----+-------------+---------+-------+---------------+----------+---------+------+--------+--------------------------+
|  1 | SIMPLE      | myNames | range | lastname      | lastname | 604     | NULL | 226800 | Using where; Using index |
+----+-------------+---------+-------+---------------+----------+---------+------+--------+--------------------------+

【讨论】:

很抱歉,您误会了我。我知道单独使用first_name 条件会导致全表扫描。我在问,当 last_name 使用通配符时,它是否有助于更快地找到索引。我希望看到更新的问题以获得更好的清晰度。我只是想弄清楚复合键最左侧列上使用的通配符是否会强制进行部分键查找(忽略索引的其余列条件)。 我试图回答不断变化的问题。这是我的最后一次尝试。 对@Drew 的困惑感到抱歉。感谢您花时间更新您的答案,+1。我认为它也会帮助其他人。 :-) 我说Run your actual query thru explain,给出了检查的参考,并显示了输出,我当然知道什么是覆盖索引。这就是为什么我介绍了一个未涵盖的专栏。我不回答这个问题了。【参考方案2】:

@Drew 所说的几乎所有内容都假定索引是“覆盖”的。

INDEX(last_name, first_name)

是一个“覆盖”索引

SELECT COUNT(*)   FROM t WHERE first_name LIKE 'joh%' AND last_name LIKE 'smi%'.
SELECT last_name  FROM t WHERE first_name LIKE 'joh%' AND last_name LIKE 'smi%'.
SELECT id         FROM t WHERE first_name LIKE 'joh%' AND last_name LIKE 'smi%'. -- if the table is InnoDB and `id` is the `PRIMARY KEY`.

但它不是“覆盖”

SELECT foo ...
SELECT foo, last_name ...
etc.

这是因为foo 未包含在索引中。对于非覆盖情况,答案完全不同:

Q1:【题目问题】复合索引最左列的通配符是否意味着索引中的剩余列不用于索引查找(MySQL)?

A1:是的,它确实是这个意思。

Q2:last_name条件中使用的通配符是否意味着first_name条件在进一步帮助MySQL查找索引时不再使用?

A2:我迷失在模糊中。优化器将查看所有索引,而不仅仅是有问题的索引。它会选择“最好的”。

Q3:换句话说,通过在 last_name 条件上放置通配符,MySQL 将只进行部分索引查找(并忽略 last_name 右侧的列中给出的条件)?

A3:是的。这似乎是 Q1 的重复。

Q4:...Example-1 会比 Example-2 快吗?

A4:没有。在极端情况下,INDEX(last_name) 会比INDEX(last_name, first_name) 慢。任一示例都将仅使用索引的第一部分 (last_name)。但是,磁盘上的复合索引更大。对于一个巨大的表,这可能导致它被缓存的百分比较小,因此磁盘命中率更高,因此速度更慢。

【讨论】:

评论不用于扩展讨论;这个对话是moved to chat。【参考方案3】:

我已经确认 Rick James 的上述回答是正确的。但是,Drew 和 Rick James 指出,根据我的 SELECT,我可以使用覆盖索引。

关于使用通配符时是否使用所有关键部分,MySQL 文档说here:

对于 BTREE 索引,间隔可能可用于组合条件 使用 AND,其中每个条件将关键部分与常数进行比较 使用 =、、IS NULL、>、=、、BETWEEN 或 LIKE 的值 'pattern'(其中 'pattern' 不以通配符开头)。一个 可以使用间隔,只要可以确定单个 包含所有符合条件的行的键元组(或两个 如果使用 或 != 则间隔)。

优化器尝试使用其他关键部分来确定 只要比较运算符是 =、 或 IS NULL,则间隔。如果 运算符是 >、=、、BETWEEN 或 LIKE,优化器 使用它,但不再考虑关键部分。 对于以下表达式, 优化器使用第一次比较中的 =。它还使用 >= from 第二个比较,但不考虑其他关键部分,不考虑 使用第三个比较进行区间构造

key_part1 = 'foo' AND key_part2 >= 10 AND key_part3 > 10

单个区间为:

('foo',10,-inf)

创建的区间可能包含的行数多于 初始条件。例如,前面的区间包括 value('foo', 11, 0),不满足原条件。

在组合的关键部分上使用 LIKE 时,不使用右侧的关键部分。这使得我们想要为 last_name 和 first_name 使用两个单独的二级索引。我会让 MySQL 判断哪个具有更好的基数并使用它。但最后,我使用了 last_name,first_name,person_id 的覆盖索引,因为我只打算做一个 SELECT person_id 并且它充当覆盖键(除了搜索 last_name 范围)。在我的测试中,这被证明是最快的。

【讨论】:

以上是关于复合索引最左列中的通配符是不是意味着索引中的剩余列不用于索引查找(MySQL)?的主要内容,如果未能解决你的问题,请参考以下文章

MySQL索引

索引失效

Pandas中xs()函数索引复合索引数据的不同切面数据(索引复合索引中需要的数据):索引列复合索引中的一个切面索引行复合索引中的一个切面

MySQL -通过调整索引提升查询效率

MySQL 联合索引详解

MySQL联合索引生效的条件、索引失效的条件