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

时间:2015-11-29 00:36:53

标签: mysql wildcard sql-like query-performance composite-index

假设您的主要综合指数为last_name,first_name。然后,您搜索了WHERE first_name LIKE 'joh%' AND last_name LIKE 'smi%'

last_name条件中使用的通配符是否意味着first_name条件不会用于进一步帮助MySQL查找索引?换句话说,通过在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';

3 个答案:

答案 0 :(得分:1)

以下是您的问题。复数。通过改写它们(用#34;换句话说"),它们只是不同的问题。这样做并不能使响应者更容易。相反。

Q1:[标题问题]复合索引最左列中的通配符是否表示索引中的剩余列不用于索引查找(MySQL)?

A1:不,这并不意味着。

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

A2:不,这并不意味着。再加上那个问题的尾巴含糊不清。它已经知道使用什么索引可能是这种模糊性的一个分支答案。

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

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

问题4:......示例1会比示例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 |
+----+-------------+---------+-------+---------------+----------+---------+------+--------+--------------------------+

答案 1 :(得分:1)

几乎@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:我在模糊中丢失了。优化器将查看所有索引,而不仅仅是相关的索引。它将选择“最佳”。

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

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

问题4:......示例-1会比示例2更快吗?

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

答案 2 :(得分:0)

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

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

  

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

     

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

     

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

     

单个间隔是:

     

(' foo',10,-inf)< (key_part1,key_part2,key_part3)< ('富',+ INF,+ INF)

     

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

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