为什么在 MySQL 中的 FULLTEXT 索引上 LIKE 比 MATCH...AGAINST 快 4 倍多?

gen*_*sis 12 mysql full-text-search

我不明白这个。

我有一个包含这些索引的表

PRIMARY     post_id
INDEX       topic_id
FULLTEXT    post_text
Run Code Online (Sandbox Code Playgroud)

表有(仅)346 000 行。我正在尝试执行 2 个查询。

SELECT post_id 
FROM phpbb_posts 
WHERE topic_id = 144017 
AND post_id != 155352 
AND MATCH(post_text) AGAINST('http://rapidshare.com/files/5494794/photo.rar')
Run Code Online (Sandbox Code Playgroud)

需要 4.05 秒,而

SELECT post_id 
FROM phpbb_posts 
WHERE topic_id=144017 
AND post_id != 155352 
AND post_text LIKE ('%http://rapidshare.com/files/5494794/photo.rar%')
Run Code Online (Sandbox Code Playgroud)

需要 0.027 秒。

EXPLAIN 显示唯一的区别在于可能的键(fulltext包含 post_text,LIKE不包含)

这真的很奇怪。

这背后是什么?后台发生了什么?LIKE不使用索引时怎么会这么快,而使用它的索引时 FULLTEXT怎么会这么慢?

更新1:

实际上现在大约需要 0.5 秒,也许表被锁定了,但是,当我打开分析时,它显示 FULLTEXT INITIALIZATION 花了 0.2 秒。这是怎么回事?

我可以LIKE每秒 10 倍的速度查询我的表格,而全文只有 2 倍

更新2:

惊喜!

mysql> SELECT post_id FROM phpbb_posts WHERE post_id != 2 AND topic_id = 6 AND MATCH(post_text) AGAINST ('rapidshare.com');
Empty set (0.04 sec)
Run Code Online (Sandbox Code Playgroud)

所以我问,这怎么可能?

此外,

SELECT count(*) FROM phpbb_posts WHERE MATCH(post_text) AGAINST ('rapidshare.com')
Run Code Online (Sandbox Code Playgroud)

真的很慢。可以全文任意破吗?

更新3:

我勒个去?

SELECT forum_id, post_id, topic_id, post_text  FROM phpbb_posts  WHERE MATCH(post_text) AGAINST ('rapidshare.com') LIMIT 0, 30;
Run Code Online (Sandbox Code Playgroud)

需要 0.27s 而

SELECT count(*) FROM phpbb_posts  WHERE MATCH(post_text) AGAINST ('rapidshare.com') LIMIT 0, 30;
Run Code Online (Sandbox Code Playgroud)

需要30多秒!这里出了什么问题?

Rol*_*DBA 2

我认为问题可能源于 FULLTEXT 索引本身的存在。

每次有涉及 FULLTEXT 索引的查询时,MySQL 查询优化器都会将该查询打造成全表扫描。多年来我已经看到了这一点。我还写了一篇关于全文索引中最琐碎行为的早期文章

您可能需要做两件事:

  1. 重构查询,以便 FULLTEXT 索引不会使 MySQL 查询优化器陷入混乱状态
  2. 添加一个额外的索引来正确支持重构的查询

重构查询

这是您的原始查询

SELECT post_id  
FROM phpbb_posts  
WHERE topic_id = 144017  
AND post_id != 155352  
AND MATCH(post_text) AGAINST('http://rapidshare.com/files/5494794/photo.rar') 
Run Code Online (Sandbox Code Playgroud)

您将需要像这样重构查询:

SELECT subqueryA.post_id
FROM
(
    SELECT post_id FROM phpbb_posts
    WHERE topic_id = 144017
    AND post_id != 155352
) subqueryA
INNER JOIN
(
    SELECT post_id FROM phpbb_posts
    WHERE MATCH(post_text) AGAINST('http://rapidshare.com/files/5494794/photo.rar')
) subqueryB
USING (post_id);
Run Code Online (Sandbox Code Playgroud)

创建新索引

您将需要一个索引来支持subqueryA。您已经在 上有一个索引topic_id。您需要按如下方式替换它:

ALTER TABLE phpbb_posts ADD INDEX topic_post_ndx (topic_id,post_id);
ALTER TABLE phpbb_posts DROP INDEX topic_id;
Run Code Online (Sandbox Code Playgroud)

试一试 !!!

更新 2012-03-19 13:08 美国东部时间

首先尝试这个

SELECT post_id FROM
(
    SELECT * FROM phpbb_posts
    WHERE topic_id = 144017
    AND post_id != 155352
) A;
Run Code Online (Sandbox Code Playgroud)

如果它运行得很快并且返回少量行,那么尝试这个嵌套子查询:

SELECT post_id FROM
(
    SELECT * FROM phpbb_posts
    WHERE topic_id = 144017
    AND post_id != 155352
) A
WHERE MATCH(post_text) AGAINST('http://rapidshare.com/files/5494794/photo.rar');
Run Code Online (Sandbox Code Playgroud)

更新 2012-03-19 13:11 美国东部时间

比较一下这个的运行时间:

SELECT count(*) FROM phpbb_posts  WHERE MATCH(post_text) AGAINST ('rapidshare.com') LIMIT 0, 30;
Run Code Online (Sandbox Code Playgroud)

有了这个

SELECT count(*) FROM phpbb_posts WHERE 1 = 1;
Run Code Online (Sandbox Code Playgroud)

如果运行时间相同,则 MATCH 子句将在每一行上执行。正如我之前提到的,使用 FULLTEXT 索引往往会抵消 MySQL 查询优化器尝试和贡献的任何好处。