【问题标题】:Best index for searching both first_name and last_name with trailing wildcards?使用尾随通配符搜索 first_name 和 last_name 的最佳索引?
【发布时间】:2016-03-04 07:52:28
【问题描述】:

我的第一个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) 的 2 个单独索引似乎比使用 SQL_NO_CACHE 的 last_name(15),first_name(15) 的复合索引更快?但也许我测试错了。我也在考虑组合索引和单个名称上的索引可能会很好(如果这不会混淆优化器)?

额外问题:
考虑到我正在搜索部分词,而不是完整词,像 ElasticSearch 这样的查询会更好吗?

【问题讨论】:

  • 我想复合索引在搜索名字和姓氏的情况下会更快。但请注意,仅对姓氏的搜索不能使用 (first,last) 上的索引
  • 但是有人告诉我,在复合索引中的 last_name 上使用通配符(尾随)会使复合索引的其余部分无用(右侧的列)。
  • 查询将只使用一个索引。优化器将选择最具选择性的索引。
  • @RichardSmith 索引合并怎么样?
  • @prograhammer 我已经尝试过 AND 和 OR,但我无法哄它使用索引合并(无论如何在 5.6 上)。它用 AND 选择一个索引。我看不出索引合并对您的查询有何帮助。也许是时候升级到 5.7

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


【解决方案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。真棒迈克!但是,在我接受这个问题之前,让我让这个问题再多待一天。而对于my.cnf的设置,你主要是指innodb_buffer_pool吗?
  • 迈克,如果表单字段都是必需的(名字和姓氏),那么将两者都编入索引有什么意义?我应该只使用姓氏索引对吗?由于优化器可能不会进行索引合并,谁知道索引合并是否最好?
  • 我会同时索引两者,因为索引基数将决定应该使用哪个索引。两者兼有将使 mysql 确定它认为最好的一个,因为它在一定程度上取决于表中的数据(因此也取决于索引)。它还与索引统计信息的最新程度有关,但简短的回答是创建两个索引。
  • 是的,我是这么认为的。我只是倾向于假设 last_name 会具有更大的基数,但这只是我自己的猜测。最好让 MySQL 来判断(只要数据使用两个索引都适合 RAM)。有什么开源的东西可以更好地进行这种类型的查询吗? IE。弹性搜索?
  • 好吧,它在您的数据上运行的速度有多快,您需要多快?可能有各种各样的方法可以加快速度,但现在你正陷入过早优化c2.com/cgi/wiki?PrematureOptimization,除非你有充分的理由继续调整它。
【解决方案2】:
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.

【讨论】:

  • 太棒了!看起来INDEX(last_name, first_name, person_id) 是要走的路(没有前缀,而且我将名称字段的 varchar(255) 减少到 varchar(70))。
【解决方案3】:

补充以上来自@mikeb 和@RickJames 的答案,

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 时,不使用右侧的关键部分。因此,这证实了@mikeb 所说的两个单一索引会更好地工作,因为 MySQL 可以判断哪个具有更好的基数并使用它。 但是,我最终使用了 Rick James 的答案,并带有 last_name,first_name,person_id(删除了前缀/大小),因为我只选择了 person_id。这作为一个覆盖索引,在我的测试中与单个单独的索引一样快(可能更快),而且我可以通过 last_name 然后 first_name 进行良好的排序。无论如何,复合键通常是更好的选择。

【讨论】:

  • 很好的参考——它涵盖了大部分问题。
【解决方案4】:

好像用钥匙了?!?

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

【讨论】:

  • 这是一个部分索引查找。它使用first_name 部分。您会看到检查的行数 = 6(所有行的 first_name 都带有“ja”前缀)。它没有使用完整的组合,因为它没有使用 last_name 部分。
  • @prograhammer 我明白了 - 我们知道它不使用整个索引,因为它说“使用哪里”?
  • 这是个好问题。我还不是 EXPLAIN 的大师。但我认为“使用 where”并不能帮助您了解复合索引是否被充分使用。它确实告诉您在索引 f1 上使用了一个范围,其中包含 6 个检查行。但老实说,我什至不确定检查的行是最好的了解方式,因为这并不总是准确的。但我刚刚发现了这个dev.mysql.com/doc/refman/5.7/en/…
  • "只要比较运算符为 =、 或 IS NULL,优化器就会尝试使用附加的关键部分来确定区间。如果运算符为 >、 =、、BETWEEN 或 LIKE,优化器使用它但不再考虑关键部分。"
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多