我的页面上有实时搜索。当向服务器发送请求时,接受过滤和排序参数(开发商、发行商、流派、时间等)。问题在于它的执行需要相当长的时间,并且由于用户输入的每个字符都会触发脚本,因此这不得不引起人们的注意。如果您在搜索时省略其他参数并以开发人员为例,您将得到此查询(执行时间约为 0.14 秒,但在表单中输入时,当然运行速度较慢):
SELECT games.id AS id FROM games LEFT JOIN games_titles ON games.id=games_titles.game_id WHERE games_titles.lang=1 AND EXISTS(SELECT 1 FROM games_devs WHERE games_devs.game_id=games.id AND games_devs.dev LIKE 'de%') ORDER BY games.rating DESC LIMIT 6
解释:
+----+--------------+--------------+--------+--------------------+---------------+---------+------------------+-------+------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+--------------+--------------+--------+--------------------+---------------+---------+------------------+-------+------------------------------------+
| 1 | PRIMARY | games | index | PRIMARY | rating_index | 8 | NULL | 1 | Using index |
+----+--------------+--------------+--------+--------------------+---------------+---------+------------------+-------+------------------------------------+
| 1 | PRIMARY | <subquery2> | eq_ref | distinct_key | distinct_key | 4 | func | 1 | Using where |
+----+--------------+--------------+--------+--------------------+---------------+---------+------------------+-------+------------------------------------+
| 1 | PRIMARY | games_titles | ref | game_id_index,lang | game_id_index | 4 | test_db.games.id | 1 | Using index condition; Using where |
+----+--------------+--------------+--------+--------------------+---------------+---------+------------------+-------+------------------------------------+
| 2 | MATERIALIZED | games_devs | range | PRIMARY,dev | dev | 452 | NULL | 49994 | Using where; Using index |
+----+--------------+--------------+--------+--------------------+---------------+---------+------------------+-------+------------------------------------+
分析:
+----+------------------------+--------+
| 1 | Starting | 103 µs |
+----+------------------------+--------+
| 2 | Checking Permissions | 16 µs |
+----+------------------------+--------+
| 3 | Opening Tables | 32 µs |
+----+------------------------+--------+
| 4 | After Opening Tables | 11 µs |
+----+------------------------+--------+
| 5 | System Lock | 11 µs |
+----+------------------------+--------+
| 6 | Table Lock | 25 µs |
+----+------------------------+--------+
| 7 | Init | 48 µs |
+----+------------------------+--------+
| 8 | Optimizing | 50 µs |
+----+------------------------+--------+
| 9 | Statistics | 145 µs |
+----+------------------------+--------+
| 10 | Preparing | 92 µs |
+----+------------------------+--------+
| 11 | Sorting Result | 23 µs |
+----+------------------------+--------+
| 12 | Executing | 46 µs |
+----+------------------------+--------+
| 13 | Query End | 14 µs |
+----+------------------------+--------+
| 14 | Removing Tmp Table | 13 µs |
+----+------------------------+--------+
| 15 | Query End | 9 µs |
+----+------------------------+--------+
| 16 | Commit | 11 µs |
+----+------------------------+--------+
| 17 | Closing Tables | 10 µs |
+----+------------------------+--------+
| 18 | Unlocking Tables | 9 µs |
+----+------------------------+--------+
| 19 | Closing Tables | 15 µs |
+----+------------------------+--------+
| 20 | Starting Cleanup | 9 µs |
+----+------------------------+--------+
| 21 | Freeing Items | 19 µs |
+----+------------------------+--------+
| 22 | Updating Status | 73 µs |
+----+------------------------+--------+
| 23 | Reset For Next Command | 19 µs |
+----+------------------------+--------+
由于我在网站上有分页,我需要获取所有条目的数量,以便我可以转到最后一页。但在这种情况下,性能受到的影响更大(在页面上搜索时需要 1.97 秒):
SELECT games.id AS id FROM games LEFT JOIN games_titles ON games.id=games_titles.game_id WHERE games_titles.lang=1 AND EXISTS(SELECT 1 FROM games_devs WHERE games_devs.game_id=games.id AND games_devs.dev LIKE 'de%') ORDER BY games.rating
解释:
+----+--------------+--------------+--------+--------------------+---------------+---------+------------------+-------+------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+--------------+--------------+--------+--------------------+---------------+---------+------------------+-------+------------------------------------+
| 1 | PRIMARY | games | index | PRIMARY | rating_index | 8 | NULL | 99889 | Using index |
+----+--------------+--------------+--------+--------------------+---------------+---------+------------------+-------+------------------------------------+
| 1 | PRIMARY | <subquery2> | eq_ref | distinct_key | distinct_key | 4 | func | 1 | Using where |
+----+--------------+--------------+--------+--------------------+---------------+---------+------------------+-------+------------------------------------+
| 1 | PRIMARY | games_titles | ref | game_id_index,lang | game_id_index | 4 | test_db.games.id | 1 | Using index condition; Using where |
+----+--------------+--------------+--------+--------------------+---------------+---------+------------------+-------+------------------------------------+
| 2 | MATERIALIZED | games_devs | range | PRIMARY,dev | dev | 452 | NULL | 49994 | Using where; Using index |
+----+--------------+--------------+--------+--------------------+---------------+---------+------------------+-------+------------------------------------+
分析:
+----+------------------------+--------+
| 1 | Starting | 150 µs |
+----+------------------------+--------+
| 2 | Checking Permissions | 19 µs |
+----+------------------------+--------+
| 3 | Opening Tables | 30 µs |
+----+------------------------+--------+
| 4 | After Opening Tables | 12 µs |
+----+------------------------+--------+
| 5 | System Lock | 11 µs |
+----+------------------------+--------+
| 6 | Table Lock | 13 µs |
+----+------------------------+--------+
| 7 | Init | 57 µs |
+----+------------------------+--------+
| 8 | Optimizing | 49 µs |
+----+------------------------+--------+
| 9 | Statistics | 142 µs |
+----+------------------------+--------+
| 10 | Preparing | 93 µs |
+----+------------------------+--------+
| 11 | Sorting Result | 22 µs |
+----+------------------------+--------+
| 12 | Executing | 21 µs |
+----+------------------------+--------+
| 13 | Sending Data | 182 ms |
+----+------------------------+--------+
| 14 | End Of Update Loop | 28 µs |
+----+------------------------+--------+
| 15 | Removing Tmp Table | 790 µs |
+----+------------------------+--------+
| 16 | End Of Update Loop | 15 µs |
+----+------------------------+--------+
| 17 | Query End | 9 µs |
+----+------------------------+--------+
| 18 | Commit | 11 µs |
+----+------------------------+--------+
| 19 | Closing Tables | 10 µs |
+----+------------------------+--------+
| 20 | Unlocking Tables | 9 µs |
+----+------------------------+--------+
| 21 | Closing Tables | 17 µs |
+----+------------------------+--------+
| 22 | Starting Cleanup | 9 µs |
+----+------------------------+--------+
| 23 | Freeing Items | 19 µs |
+----+------------------------+--------+
| 24 | Updating Status | 75 µs |
+----+------------------------+--------+
| 25 | Reset For Next Command | 18 µs |
+----+------------------------+--------+
CREATE TABLE `games` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`rating` double NOT NULL DEFAULT 0,
`date` date DEFAULT NULL,
`date_type` int(1) NOT NULL DEFAULT 1,
`img` varchar(500) DEFAULT NULL,
`img_type` int(10) NOT NULL,
`url` varchar(100) NOT NULL,
PRIMARY KEY (`id`),
KEY `rating_index` (`rating`) USING BTREE,
KEY `date_index` (`date`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=326678 DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci
CREATE TABLE `games_titles` (
`game_id` int(50) NOT NULL,
`title` varchar(150) DEFAULT NULL,
`lang` int(10) NOT NULL DEFAULT 1,
KEY `game_id_index` (`game_id`) USING BTREE,
KEY `lang` (`lang`,`title`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci
CREATE TABLE `games_devs` (
`id` int(50) NOT NULL AUTO_INCREMENT,
`game_id` int(50) NOT NULL,
`dev` varchar(150) NOT NULL,
PRIMARY KEY (`id`),
KEY `game_id_index` (`game_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=126699 DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci
该请求还会按语言进行检查,因为该网站是多语言的。但我决定省略这些细节,因为这里的主要负载仍然来自搜索本身。我还没有将索引放在“dev”字段上,其余的一切都已设置完毕。您可以看到上面表格的结构。
您能否告诉我如何提高搜索性能,或者您可以提供什么替代 LIKE 运算符的方法?我听说很多人不建议使用它。现在在“dev”表中,条目的形式为“dev_1”、“dev_2”等。如果在表单中输入字母“p”,则脚本将被触发约10毫秒,其中字母“d”当然,绕过所有选项需要更长的时间。当然,我知道在实际制作中写作会有更多变化,但这里最好的解决方案是什么?分页时,搜索时间会随着每一个新页面的增加而增加(逻辑上,因为MySQL算法需要生成一个新的数据集,但是从初始位置做一个缩进),恐怕会需要很长的时间,所以现在搜索优化是我的主要目标。
有几件事值得一提。
以您描述的风格进行渐进式自动完成搜索是一个好主意;它提供了良好的用户体验,并帮助人们快速找到他们想要的东西。
column LIKE '%searchterm%'
的
%
是一种臭名昭著的查询性能反模式。避免它。
在网页的 Javascript 代码中,避免每次击键都发送新的搜索请求是一个好主意。如果您编写一些 JavaScript 来在最后一次击键后一段固定的时间内发送累积的搜索数据(由用户键入),您的情况会更好。因此,举例来说,如果我打字速度很快,我可以在您发送单词之前输入“优化”一词。但如果我在“optimi”之后犹豫,你会发送它。几百毫秒的延迟是一个很好的延迟,但您可以尝试一下。
这减轻了服务器搜索负担,并避免了过于繁忙的用户体验。 (值得一提的是,jQueryUI 自动完成小部件中的远程数据源代码内置了这个计时逻辑;他们在解决各种细节方面做得很好。)
相关(依赖)子查询值得避免。
从您的示例中获取此查询。
SELECT games.id AS id
FROM games
LEFT JOIN games_titles ON games.id=games_titles.game_id
WHERE games_titles.lang=1
AND EXISTS( SELECT 1
FROM games_devs
WHERE games_devs.game_id=games.id
AND games_devs.dev LIKE 'de%'
)
ORDER BY games.rating DESC LIMIT 6
If you refactor it like this it will be faster. JOIN with MAX gets you the same filtering behavior as WHERE EXISTS.
```sql
SELECT games.id AS id
FROM games
JOIN ( SELECT MAX(game_id) game_id
FROM games_devs
WHERE dev LIKE 'de%
) AS does_dev_exist
ON does_dev_exist.game_id = games.id
WHERE games_titles.lang=1
ORDER BY games.rating DESC LIMIT 6
该索引将有助于加速细化子查询。
CREATE INDEX dev_game_id ON games_devs(dev, game_id);
表通常需要与您使用的查询相匹配的多列索引。每个子查询(EXPLAIN 的每一行)在 MySQL 中只能使用一个索引。
除非使用您使用的精确查询,否则您需要的索引是不可知的。这个特定的索引将帮助您在示例查询中查找 games_titles。
CREATE INDEX lang_game_id ON games_titles(lang, game_id);
要有效获取总(非分页)结果集计数,请执行 SELECT COUNT(*) 并跳过 ORDER BY 操作。除非您知道需要它,否则请始终跳过 ORDER BY。更好的是,如果您能弄清楚如何避免在自动完成 UI 中需要总结果集计数,那就这样做。