页面实时搜索速度慢(MySQL InnoDB)

问题描述 投票:0回答:1

我的页面上有实时搜索。当向服务器发送请求时,接受过滤和排序参数(开发商、发行商、流派、时间等)。问题在于它的执行需要相当长的时间,并且由于用户输入的每个字符都会触发脚本,因此这不得不引起人们的注意。如果您在搜索时省略其他参数并以开发人员为例,您将得到此查询(执行时间约为 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算法需要生成一个新的数据集,但是从初始位置做一个缩进),恐怕会需要很长的时间,所以现在搜索优化是我的主要目标。

mysql performance query-optimization sql-like
1个回答
0
投票

有几件事值得一提。

  1. 以您描述的风格进行渐进式自动完成搜索是一个好主意;它提供了良好的用户体验,并帮助人们快速找到他们想要的东西。

  2. 带有前导
  3. column LIKE '%searchterm%'

    %
    是一种臭名昭著的查询性能反模式。避免它。

  4. 在网页的 Javascript 代码中,避免每次击键都发送新的搜索请求是一个好主意。如果您编写一些 JavaScript 来在最后一次击键后一段固定的时间内发送累积的搜索数据(由用户键入),您的情况会更好。因此,举例来说,如果我打字速度很快,我可以在您发送单词之前输入“优化”一词。但如果我在“optimi”之后犹豫,你会发送它。几百毫秒的延迟是一个很好的延迟,但您可以尝试一下。

    这减轻了服务器搜索负担,并避免了过于繁忙的用户体验。 (值得一提的是,jQueryUI 自动完成小部件中的远程数据源代码内置了这个计时逻辑;他们在解决各种细节方面做得很好。)

  5. 相关(依赖)子查询值得避免。

    从您的示例中获取此查询。

    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);
  1. 表通常需要与您使用的查询相匹配的多列索引。每个子查询(EXPLAIN 的每一行)在 MySQL 中只能使用一个索引。

    除非使用您使用的精确查询,否则您需要的索引是不可知的。这个特定的索引将帮助您在示例查询中查找 games_titles。

    CREATE INDEX lang_game_id ON games_titles(lang, game_id);
    
  2. 要有效获取总(非分页)结果集计数,请执行 SELECT COUNT(*) 并跳过 ORDER BY 操作。除非您知道需要它,否则请始终跳过 ORDER BY。更好的是,如果您能弄清楚如何避免在自动完成 UI 中需要总结果集计数,那就这样做。

© www.soinside.com 2019 - 2024. All rights reserved.