【问题标题】:How Can I Optimize My MySQL Select Query?如何优化我的 MySQL 选择查询?
【发布时间】:2019-08-17 09:24:58
【问题描述】:

我有一个包含超过 90,000 行的数据库表。

每一行都包含一个词,可以是形容词、副词、名词或动词(如类型列所示)。

我需要运行一个 MySQL 查询,它返回一小部分(例如 3 - 10)随机选择的名词,其中单词的长度在两个提供的数字之间(例如 4 - 8)。

我尝试过的查询的性能不是很好。

这是表结构:

CREATE TABLE `words` (
  `id` int(11) NOT NULL,
  `type` char(1) COLLATE utf8_unicode_ci NOT NULL,
  `word` varchar(50) COLLATE utf8_unicode_ci NOT NULL,
  `variations` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `free_writing_prompt` smallint(1) NOT NULL DEFAULT '0',
  `word_length` smallint(5) NOT NULL DEFAULT '0'
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

ALTER TABLE `words`
  ADD PRIMARY KEY (`id`),
  ADD UNIQUE KEY `word_type` (`type`,`word`),
  ADD KEY `type` (`type`),
  ADD KEY `Word Length` (`word_length`);

ALTER TABLE `words`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

free_writing_prompt 列设置为 0 或 1,1 表示它是查询的有效选项,0 表示忽略它。目前,所有名词都将此列设置为 1,但计划是将许多单词更改为 0,因为它们不是应该返回的名词。

这是按类型划分的行数:

形容词:21,499
副词:4,475
名词:58,670
动词:8,978

这是我尝试的第一个查询:

SELECT  word
    FROM  words
    WHERE  type='n'
      AND  free_writing_prompt=1
      AND  CHAR_LENGTH(word)>=4
      AND  CHAR_LENGTH(word)<=8
    ORDER BY  RAND()
    LIMIT  3;

在此之后,我决定添加 word_length 列,它最初不是表定义的一部分,因为当我可以预先计算并存储该值时,对所有这些行使用 CHAR_LENGTH 函数似乎效率低下。

我还认为值得在 word_length 列上建立一个 Key 索引,但我不相信它真的有帮助。

然后我尝试了以下三个查询,但它们仍然需要大约 16 秒才能运行:

SELECT  word
    FROM  words
    WHERE  type='n'
      AND  free_writing_prompt=1
      AND  word_length>=4
      AND  word_length<=8
    ORDER BY  RAND()
    LIMIT  3;
SELECT  word
    FROM  words
    WHERE  type='n'
      AND  free_writing_prompt=1
      AND  word_length IN (
        SELECT  word_length
            FROM  words
            WHERE  word_length>=4
              AND  word_length<=8
                          )
    ORDER BY  RAND()
    LIMIT  3;
SELECT  word
    FROM  words
    WHERE  word_length IN (
        SELECT  word_length
            FROM  words
            WHERE  type='n'
              AND  free_writing_prompt=1
              AND  word_length>=4
              AND  word_length<=8
                          )
    ORDER BY  RAND()
    LIMIT  3;

然后我尝试了这个联合查询,它稍微快了一点(但可能不足以具有统计意义):

SELECT * FROM (
    SELECT word FROM words WHERE type='n' AND free_writing_prompt=1 AND word_length=4
    UNION
    SELECT word FROM words WHERE type='n' AND free_writing_prompt=1 AND word_length=5
    UNION
    SELECT word FROM words WHERE type='n' AND free_writing_prompt=1 AND word_length=6
    UNION
    SELECT word FROM words WHERE type='n' AND free_writing_prompt=1 AND word_length=7
    UNION
    SELECT word FROM words WHERE type='n' AND free_writing_prompt=1 AND word_length=8
) a
ORDER BY RAND() LIMIT 3;

有人对如何优化此查询以提高运行时间有任何想法吗?

【问题讨论】:

  • 拥有一个覆盖索引可能会有所帮助,例如:(free_writing_prompt, type, word_length[, word]);如果执行 order-by-take-then-join,应该能够将word 排除在外而几乎没有影响(即加入只需要进行 3 次探测) - 查询规划器说正在发生什么?另外,总的来说,我认为“ORDER BY RAND()”有“更有利”的解决方案。
  • 注意覆盖索引的顺序是特定的:free_writing_prompttype 都是直接相等的,而word_length 是一个范围(不管它是如何写的;应该只使用AND word_length &gt;= 4 AND word_length &lt; 8删除所有联合以覆盖操作;在这里通过多个直接相等来执行 SQL 没有好处)
  • 谢谢 - 我以前从来没有担任过覆盖索引的负责人。

标签: mysql query-optimization


【解决方案1】:

复合索引

    WHERE  type='n'
      AND  free_writing_prompt=1
      AND  word_length>=4
      AND  word_length<=8

祈求

INDEX(type, free_writing_prompt,   -- in either order
      word_length)                 -- last

更好的是,将其拉伸成一个覆盖索引

INDEX(type, free_writing_prompt,   -- in either order
      word_length,                 -- last (for indexing purposes)
      word)                        -- to make "covering"

您的第一个SELECT 将与此覆盖索引很好地配合。唉,它不会是完美的,因为它会构建一个数千个临时表,对其进行排序,然后剥离 3 行。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-02-08
    • 1970-01-01
    • 2012-11-10
    • 1970-01-01
    • 2013-07-06
    • 2012-10-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多