使用尾随通配符搜索first_name和last_name的最佳索引?

时间:2015-11-30 15:56:10

标签: mysql indexing wildcard query-performance composite-key

我在问题上的第一个attempt被证明是混乱的,我得到了一些混合的答案(可能是由于我的疑惑问题)。这是一个不同的更好的问题......

假设我的表在MySQL中看起来像这样:

CREATE TABLE `people` (
    `person_id` INT(11),
    `alias_num` TINYINT(3),
    `first_name` VARCHAR(255) NOT NULL,
    `last_name` VARCHAR(255) NOT NULL,
    PRIMARY KEY (`person_id`,`alias_num`)
  )
COLLATE='latin1_swedish_ci'
ENGINE=InnoDB;

使用这样的数据:

person_id alias_num first_name last_name
--------- --------- ---------- ---------
1         1         John       Smith
2         1         Joe        Smith
3         1         Bill       Smith     # <-- Notice this guy has 3 aliases
3         2         Billy      Smith     # <--
3         3         William    Smith     # <--
4         1         Susan      Thompson
...

假设josmi已输入HTML搜索表单(需要两个字段),我的查询将始终如下:

SELECT person_id FROM people WHERE first_name LIKE 'jo%' AND last_name LIKE 'smi%';

问题:为了使上述查询最快,添加到我的表中的最佳索引是什么?

注意: 我对几乎一百万行的表做了一些快速测试,看起来first_name(15)last_name(15)的两个单独索引看起来比使用SQL_NO_CACHE的last_name(15),first_name(15)的复合索引更快?但也许我正在测试这个错误。我也在考虑,复合索引和单个名称上的索引的组合可能会很好(如果这不会混淆优化器)?

奖金问题:
考虑到我正在搜索部分单词而非完整单词,ElasticSearch会更好地进行此查询吗?

4 个答案:

答案 0 :(得分:1)

您是正确的,单独的first_name和last_name索引将更好地工作。

根据我的经验,复合索引最适合非变量字段(如2个数字)。我在每个名字字段上都有一个索引。

如果您还没有调整my.cnf设置,调整MySQL可用的内存可能会对索引的排序/搜索产生巨大差异。

至于my.cnf,那是另外一个问题,IMO。你可以从这里开始:https://dev.mysql.com/doc/refman/5.6/en/server-default-configuration-file.html。 Mysql附带my-large.cnf,my-huge.cnf所以那些应该给你一个良好的开端。

答案 1 :(得分:1)

从@mikeb和@RickJames添加上述答案,

MySQL文档说here

  

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

     

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

     

key_part1 ='foo'AND key_part2&gt; = 10 AND key_part3&gt; 10

     

单个间隔是:

     

('foo',10,-inf)&lt; (key_part1,key_part2,key_part3)&lt; ( '富',+ INF,+ INF)

     

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

在复合的关键部分使用LIKE时,不使用右侧的关键部分。因此,这证实了@mikeb所说的两个单一索引可以更好地工作,因为MySQL可以判断哪一个具有更好的基数并使用它。 然而,由于我只选择了person_id,我最终使用了Rick James的答案 last_name,first_name,person_id(前缀/大小已删除)。这充当覆盖索引,并且在我的测试中的工作速度(可能更快)比单个单独的索引更快,并且通过last_name然后first_name给我很好的排序。复合键通常是更好的方式。

答案 2 :(得分:1)

SELECT person_id FROM people WHERE first_name LIKE 'jo%' AND last_name LIKE 'smi%';

案例1 - 覆盖(罕见):所有 整个 SELECT的字段都包含在索引中。这些都是“覆盖”和最佳:

INDEX(first_name, last_name, person_id)
INDEX(last_name, first_name, person_id)
<覆>“覆盖”意味着它完成了索引中的所有工作,并且不需要触摸数据。注意:“数据”和PRIMARY KEY共同生活在一个BTree中;每个二级索引都存在于另一个BTree中。

案例2 - 非覆盖:如果您不想或不能(因为TEXT等)包含所有字段,则其中任何一个都是最佳:

INDEX(first_name)
INDEX(last_name)

创建两个索引并让优化器动态选择更好的索引。由于外卡,INDEX(first_name, last_name)没有用;它不会越过索引的第一列。

前缀使用first_name(15)。它不会节省太多空间,并且有助于提高性能。与案例2一样,将超过复合索引中的第一列。

(255):不要随意使用VARCHAR(255)。 255参与了可能用于执行SELECT的临时表的详细信息,并且您将减慢查询的速度,以及合理的最大长度。在某些情况下,您将超出限制,不允许构建索引。

辅助密钥:在InnoDB中,每个“辅助密钥”都隐式包含PRIMARY KEY中的所有列。因此,INDEX(first_name, last_name)实际上会包含person_id(和alias_num),因此与我推荐的INDEX(first_name, last_name, person_id)相同。

INDEX(a)和INDEX(a,b):前者实际上总是多余的;只保留后者。

my.cnf :此讨论最重要的设置是将innodb_buffer_pool_size设置为可用 RAM的约70%。

进一步讨论Building an index from a SELECTCompound indexes

答案 3 :(得分:0)

似乎使用了密钥?!?

DROP TABLE IF EXISTS my_table;

CREATE TABLE my_table
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,first_name VARCHAR(12) NOT NULL
,last_name VARCHAR(12) NOT NULL
,INDEX fl (first_name,last_name)
);

INSERT INTO my_table (first_name,last_name) VALUES
('John','Brown'),
('John','Smith'),
('John','Johnson'),
('John','Lewis'),
('John','Lennon'),
('John','Major'),
('James','Brown'),
('James','McIlroy'),
('James','Napier'),
('Jamie','Oliver'),
('James','May'),
('James','Martin');

SELECT * FROM my_table WHERE first_name LIKE 'Ja%' AND last_name LIKE 'Bro%';
+----+------------+-----------+
| id | first_name | last_name |
+----+------------+-----------+
|  7 | James      | Brown     |
+----+------------+-----------+

EXPLAIN 
SELECT * FROM my_table WHERE first_name LIKE 'Ja%' AND last_name LIKE 'Bro%';
+----+-------------+----------+-------+---------------+------+---------+------+------+----------+--------------------------+
| id | select_type | table    | type  | possible_keys | key  | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+----------+-------+---------------+------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | my_table | range | fl            | fl   | 28      | NULL |    6 |   100.00 | Using where; Using index |
+----+-------------+----------+-------+---------------+------+---------+------+------+----------+--------------------------+