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

Best index for searching both first_name and last_name with trailing wildcards?

我的第一个 问题被证明是令人困惑的,我收到了一些混合的答案(可能是由于我的问题令人困惑)。这是一个不同的更好的问题...

假设我的 table 在 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%';

问题:添加到我的 table 中以使上述查询最快的最佳索引是什么?

注意: 我对将近一百万行的 table 进行了一些快速测试,看起来 first_name(15)last_name(15) 的 2 个单独索引似乎比 [= 的复合索引更快=19=] 使用 SQL_NO_CACHE?但也许我测试错了。我也在考虑将复合索引和单个名称的索引结合起来可能会很好(如果这不会混淆优化器)?

加分题:
考虑到我搜索的是部分单词,而不是完整单词,像 ElasticSearch 这样的工具会更好地执行此查询吗?

你是对的,单独的 first_name 和 last_name 索引会更好。

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

如果您还没有调整 my.cnf 设置,您也可以调整,调整 MySQL 可用的内存可以在索引 sorting/searching 中产生显着差异。

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

好像用了钥匙?!?

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 |
+----+-------------+----------+-------+---------------+------+---------+------+------+----------+--------------------------+

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

MySQL 文档说 here:

For a BTREE index, an interval might be usable for conditions combined with AND, where each condition compares a key part with a constant value using =, <=>, IS NULL, >, <, >=, <=, !=, <>, BETWEEN, or LIKE 'pattern' (where 'pattern' does not start with a wildcard). An interval can be used as long as it is possible to determine a single key tuple containing all rows that match the condition (or two intervals if <> or != is used).

The optimizer attempts to use additional key parts to determine the interval as long as the comparison operator is =, <=>, or IS NULL. If the operator is >, <, >=, <=, !=, <>, BETWEEN, or LIKE, the optimizer uses it but considers no more key parts. For the following expression, the optimizer uses = from the first comparison. It also uses >= from the second comparison but considers no further key parts and does not use the third comparison for interval construction:

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

The single interval is:

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

It is possible that the created interval contains more rows than the initial condition. For example, the preceding interval includes the value ('foo', 11, 0), which does not satisfy the original condition.

在组合的关键部分使用LIKE 时,右侧的关键部分不会被使用。所以这证实了 @mikeb 所说的两个单一索引会更好,因为 MySQL 可以判断哪个具有更好的基数并使用它。 但是,我最终使用了 Rick James 的答案并删除了 last_name,first_name,person_id(删除了 prefix/size),因为我只选择了 person_id。这充当覆盖索引,在我的测试中与单个单独的索引一样快(可能更快),而且让我按 last_name 然后 first_name 很好地排序。复合键通常是更好的方式。

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

案例 1 - 覆盖(罕见):全部整个的字段SELECT被收录在索引中。其中任何一个都是 "covering" 并且是最优的:

INDEX(first_name, last_name, person_id)
INDEX(last_name, first_name, person_id)

"Covering" 意味着它在索引内部完成所有工作,不需要触及数据。注意:"Data" 和 PRIMARY KEY 共存于一个 BTree 中;每个二级索引都存在于另一个 BTree 中。

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

INDEX(first_name)
INDEX(last_name)

创建两个索引并让优化器动态选择更好的一个。 INDEX(first_name, last_name)因为通配符没有用;它不会超过索引的第一列。

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

(255):不要乱用VARCHAR(255)。 255 涉及可能用于执行 SELECT 的临时 table 的详细信息,您将减慢对合理的最大长度会发生什么的查询。在某些情况下,您将超过限制而不允许建立索引。

二级键:在InnoDB中,每个"secondary key"隐含地包含来自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设置为available[=63的70%左右=]内存。

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