【问题标题】:Use multiple indexes with my FULLTEXT index将多个索引与我的 FULLTEXT 索引一起使用
【发布时间】:2021-03-06 14:58:06
【问题描述】:

我觉得我已经在 Stackoverflow 上阅读了 20 篇关于此的帖子,但仍然不太确定如何回答我的问题。

今天我有一个名为documents 的表,大小约为 10GB,有 400 万行。该表是一个多租户应用程序,因此有一个名为system_id 的列对每个租户进行分段。今天我有一个类似的查询

SELECT *
FROM documents
WHERE system_id = 1 AND
      status = 100 AND
      MATCH(content,notes) AGAINST ('office' IN BOOLEAN MODE);

我在这个表上创建了两个索引:

documents_index BTREE system_id,status

documents_fulltext_index FULLTEXT content,notes

据我了解,MySQL 优化器只会在此处运行其中一个索引,当我执行explain 时,我知道它将使用FULLTEXT 索引。这是否意味着查询将运行全表扫描并检查每一行是否有“office”,然后根据system_idstatus 过滤掉?在阅读this post 之后,我看到您应该尝试隔离 FULLTEXT,因为我想同时使用system_idstatusFULLTEXT 的索引(这甚至可能吗?)

SELECT B.id
    FROM (
        SELECT id
        FROM documents
        WHERE system_id = 95 AND
              status = 900
    ) A
    LEFT JOIN documents B using (id)
    WHERE MATCH(content,notes) AGAINST ('office' IN BOOLEAN MODE);

这显着加快了查询速度,但当我解释时它显示:

*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: documents
   partitions: NULL
         type: ref
possible_keys: documents_table_index
          key: documents_table_index
      key_len: 4
          ref: const
         rows: 864
     filtered: 10.00
        Extra: Using where; Using index
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: B
   partitions: NULL
         type: eq_ref
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: documents.id
         rows: 1
     filtered: 11.11
        Extra: Using where
2 rows in set, 1 warning (1.09 sec)

所以现在它正在使用system_id 索引,但它看起来不像在另一个查询中使用FULLTEXT 索引,因为它显示PRIMARY。这个查询根本没有使用FULLTEXT 索引吗?

我想要布尔搜索功能,但如果我使用 LIKE 而不是 FULLTEXT,对于拥有大约 10 000 条记录的租户来说,速度要快得多。但是,如果我尝试使用 LIKE 在文档表上查询具有例如 400 000 条记录的租户,那么 FULLTEXT 会更快。 performance analysis 对此进行了解释。

我希望它对拥有 10 000 个文件和 400 000 个文件的租户都有好处,我应该使用哪些文件? FULLTEXT 还是 LIKE?如果我能让它们具有相同的性能,我想使用FULLTEXT,因为您可以在布尔模式下执行额外的操作。

更新 1

从下面的 Rolando 和 Rick James 那里得到了一些很好的答案。

我正在使用 InnoDB 和 mysql 5.7

我有一件事很难得到。在我的情况下,感觉必须先使用system_id, status 索引,因为它会从 400 万行中“提取”出感兴趣的 10 000 行。然后您将对这 10 000 行运行 FULLTEXT 搜索。但是从我所读到的,这是不可能的?

罗兰多 我现在明白你在你的索引上强加了FULLTEXT,而我在我的上强加了system_id, status。当我像你说的那样翻转查询时,运行查询需要 3.4 秒,而强制 system_id, status 索引需要 0.54 秒。我是否正确理解 FULLTEXT 基本上是一种带有所有单词作为标记的树,这意味着当我搜索 office 时需要更长的时间,因为有很多带有 office 的文档在里面?与我搜索 ABC123(我知道它只在 1 个文档中)相比,它的速度要快得多。

我也尝试了您的其他查询 Rolando,这需要 1.3 秒,所以最快的查询仍然是我使用 system_id, status 索引的查询。至少对于像office 这样的词。我想知道该查询实际上是如何工作的。首先它使用system_id, status 索引来获取我感兴趣的10 000 行。之后它在每一行上运行布尔搜索(即不使用索引)?这意味着拥有 400 000 个文档的客户 (system_id) 的搜索速度会比搜索 10 000 个文档的速度慢(即它会随文档数量线性扩展)?这里和LIKE 有什么区别?是LIKE 会查找“字符”而FULLTEXT 仍会查找单词并使用布尔搜索功能吗?

因为如果我现在搜索 "office sverige" 而不仅仅是 office,则强制使用 system_id, status 索引的查询需要 17 秒,而仅使用 LIKE '%office sverige%' 运行简单查询需要 0.11 秒。

里克·詹姆斯 我看到您回答了我的一个问题,即FULLTEXT 变慢了,因为我敢打赌,我的数据库中有很多文档中都包含office 这个词。当我搜索我知道非常独特的东西时,查询会变得更快。我不知道我的客户会确切搜索什么,我不想告诉他们更常见的词需要更长的时间来搜索。就是这样吗?

我之所以没有选择LIKE,是因为正如你所说。前面和后面的通配符会变得非常慢,因为它必须搜索所有可能性。我认为在这种情况下它会更快,因为它使用了system_id, status 索引并且office 是一个非常常见的词。

根据您从我的问题中了解的要求,您能否为我指明运行什么查询/解决方案的正确方向?我也许可以实现类似 Sphynx och ElasticSearch 或其他的东西,但我真的不想这样做。现在我倾向于强制使用system_id, status 索引,因为我认为这在所有不同情况下都会表现最好。但是,如果我应该运行LIKEMATCH 进行查询?非常感谢您的帮助!

更新 2

里克·詹姆斯 非常感谢您的回复,我将在下面回答您的问题:

status 列可能不适合分区。在 99% 的行中,它的值将是 900。当一行不是状态 900 时,表示它现在正在处理中,并且该处理只完成一次,然后变为 900。

我总是将= 用于system_idstatus。我认为也许我可以基于system_id 进行分区,因为就像我说这是一个多租户应用程序,所以我所有的租户文档都在documents 表中,然后我有一个像system_id 这样的键(应该有被tenant_id) 分开了。

今天documents 表中有大约 400 万行,但它的增长速度很快。假设今天每天大约有 2-3000 个新文档进来,所以我想构建一个在有 2000 万个文档时可行的解决方案。

我的大多数租户最多会拥有 10-20 000 个文档,但也可能会有 500 000 个文档。这些人可以接受慢一点的搜索,但不应该太多。

我一直在想一个解决方案,虽然很激烈,但不是让我的所有租户都在同一个数据库中,而是拥有一个多租户应用程序,每个租户都有自己的数据库。然后我不必按system_id 过滤,而可以只使用FULLTEXT 索引。然而,这确实涉及大量的重建工作,我不想走那条路。

【问题讨论】:

  • 如果您知道查询将搜索的文档数量,那么您可以决定使用哪种搜索类型。我们无法告诉您哪种方法更适合您的特定情况,您需要对其进行测试。
  • 我也一直在考虑这个问题@Shadow,但我不想走那条路,因为这意味着有些人将拥有二进制搜索功能,有些人将拥有LIKE,为不同的租户提供不同的解决方案。最好的办法是使用system_id 索引不必扫描整个表,然后使用FULLTEXT 搜索那个较小的结果集。
  • 然后使用子查询返回你想要的sysids,并在外部查询中使用全文搜索。
  • 单个词搜索对全文索引无效,使用LIKEthtat 对单个词会更快
  • @Shadow 会是什么样子?我试过这个:SELECT id FROM ( SELECT * FROM documents WHERE system_id = 1 AND status = 100 ) A WHERE MATCH(content,notes) AGAINST ('office' IN BOOLEAN MODE); 但这比我展示的要慢。另外explain 表明它只是使用FULLTEXT 索引。 @nbk 它并不总是一个词。这里只是为了展示。

标签: mysql


【解决方案1】:

FULLTEXT 非常高效,即使是一个单词。如果搜索词有数千行(在您的示例中为'office'),则效率会降低。

它不会进行“全表扫描”。如果您看到此类,请提供EXPLAIN FORMAT=JSON SELECT ...,以便我们进一步深入研究。

LIKE 带有前导通配符(例如,`LIKE '%office%')非常慢,因为它进行表扫描。 (实际上,在您的情况下,忽略“办公室”的搜索并使用其他索引。)

我希望表是 InnoDB,而不是 MyISAM。注意:旧的“帖子”是在谈论 MyISAM。

将使用全文索引,而不使用其他索引。

尝试同时使用这两个索引(通过子查询、连接或其他方式)不太可能有帮助。

重新更新 1

如果数字列过滤到行的百分之几的一小部分,那会导致窘境。

  • 首先执行该过滤会导致在执行第二个 FT 过滤时遇到困难。 (我什至不知道这是否可能。)
  • 执行LIKE '%office%' 需要检查所有10K 行。如果 10K 有时是 1M,那就太贵了。
  • 同样,如果 MATCHing 'office' 导致 1M 行,那么二次过滤可能成本太高。
  • RLIKE 提供更大的功率,但速度更慢;我认为考虑它没有优势。
  • PARTITION BYsystem_id 和/或status 可能可行。这些列的分布是什么?如果其中一个值有 1M 行,则在选择它时将无济于事。在测试system_id 和/或status 时,您是否总是使用=?或者IN?还是范围?

如果分布是“合理的”,PARTITION 的工作方式如下。

  • 我们需要确定在哪个列(system_id 或 status)上进行分区。这将基于每个的频率以及有多少不同的值。
  • 我们需要弄清楚有多少个不同的分区 - 太多会导致不同的效率低下。
  • 优化器将首先对“分区键”(这两列之一)进行“分区修剪”。在该分区内将有一个单独的 FULLTEXT 索引,该索引只能查看该分区。
  • 当我听说列的分布时,我会更具体。

(我对 Sphynx 或 ElasticSearch 的了解不够多,无法发表评论。请注意:整个数据集有多大?你有多少 RAM?)

至于只有INDEX(system_id, status),然后使用LIKE——这可能很好。我会找到每一列的极端情况并运行测试以查看每个查询公式的速度有多慢。十几个测试用例可能很容易编写和测试。我认为您会发现每个查询公式(包括 Rolando 的)对于 system_id、status 和 text 的某些值都适用,而对于其他查询公式则很差。分区方法可能会使最坏的情况变得不那么糟糕,同时不会损害快速的情况。

FULLTEXT 比 LIKE 快,因为 FT 建立了 word-->row 的倒排索引,而 LIKE 每次都必须扫描每一行。

重新更新2

如果status 几乎总是 900,则以不同方式处理非 900 的情况。

A 计划:

SELECT *
FROM documents
WHERE system_id = 1
  AND status = 100
  AND ( content LIKE '%office%'
      OR notes LIKE '%office%' )

并取决于拥有INDEX(system_id, status)(任何一个订单都可以)

B计划:

不要索引状态。甚至不要直接搜索表格。取而代之的是另一个变化很大的表——它包含正在处理的记录(状态!= 900)。这个额外的表将只有几列,可能是 system_id、status 和 id 到主表中。

SELECT d.*
FROM ( SELECT id FROM in_progress
           WHERE system_id = 1
             AND status = 100 ) AS ip
JOIN documents AS d  ON d.id = ip.id
WHERE ( content LIKE '%office%'
      OR notes LIKE '%office%' )

in_progress 可能有PRIMARY KEY(system_id, status, id)

同时,向主表发送任何 900 个请求并使用 MATCH。

回到分区...有多少租户(不同的 system_id 值)?如果有 100 个,您可以考虑单独的数据库。如果有 10,000 个,事情就会变得一团糟。

PARTITION BY RANGE(system_id) 并尝试平衡它们,使每个分区的大小大致相同。满载时瞄准大约 50 个分区。 (如果system_id 是一个不断增长的整数,您可能只有十几个部分开始,然后随着数字的增长添加更多部分。)

【讨论】:

  • 非常感谢@Rick James。我现在已经发布了更新。
  • @Cous - 我添加了更多。
  • 非常感谢@Rick James。我刚刚添加了 2 号更新。非常感谢您的帮助!
  • 计划 A 听起来很诱人,因为它很容易实施。与拥有 10K 文件的租户相比,这将使拥有 409K 文件的租户的速度变慢。分区听起来很有希望。我今天有大约 100 个system_id,它们的文档数量都非常不同。有些是 400K,有些是 1K。我可以在system_id 上进行分区,然后在此分区上使用FULLTEXT 吗?那将是我想要的。首先使用system_id 过滤掉大部分记录。然后对已过滤的记录使用FULLTEXT。每个system_id 会有一个分区?再次感谢!
  • @Cous - 大多数情况下你的想法是正确的。但是每个 system_id 没有 1 个分区——今天你需要 100 个;明天你将需要 1000 个。这样的缩放效果不好。相反,我建议BY RANGE 并瞄准大约 50 个分区。开始时,您可以仔细选择范围(主要)将鲸鱼放在自己的分区中,同时将虾聚集成块。 (未来的 system_ids 无法预测;因此将它们聚集在一起,例如每个分区 20 个 ids。未来的维护可能会改变分区之间的边界。)
【解决方案2】:

您的想法有点正确,但您必须将全文搜索隔离在其自己的子查询中。在您阅读的我的帖子中,我使用了这个示例

SELECT B.*
FROM (SELECT id from ft_test
WHERE MATCH(txt) AGAINST ("+cameroon" IN BOOLEAN MODE)) A
LEFT JOIN ft_test B USING (id);

我基本上是强制在EXAPLIN计划中首先使用全文。

你的尝试看起来像这样

SELECT B.id
FROM (
    SELECT id
    FROM documents
    WHERE system_id = 95 AND
          status = 900
) A
LEFT JOIN documents B using (id)
WHERE MATCH(content,notes) AGAINST ('office' IN BOOLEAN MODE);

这将强制首先使用system_id,status 索引。查询优化器将忽略全文索引并尝试进行暴力连接。

所以,以你的例子并翻转顺序以首先使用全文索引

SELECT B.id
FROM (
    SELECT id
    FROM documents
    WHERE MATCH(content,notes) AGAINST ('office' IN BOOLEAN MODE)
) A
LEFT JOIN documents B using (id)
WHERE B.system_id = 95 AND B.status = 900;

另一个可以尝试的查询模式是这样的

SELECT A.*
FROM
(
    SELECT * FROM documents
    WHERE system_id = 95 AND status = 900
) A
INNER JOIN
(
    SELECT id FROM documents
    WHERE MATCH(content,notes)
    AGAINST ('office' IN BOOLEAN MODE)
) B
USING (id);

MySQL 查询优化器往往很顽固。我从未见过全文索引与常规索引的干净索引合并。所以,我通常推荐只检索键的全文搜索。然后,将这些键连接到其他表或同一个表。

我的处理方法有点不正统,但如果您的模式直接查询,可能会很有效。

可悲的是,由于 MySQL 查询优化器将全文索引视为三等公民(是我说的是第三个)。

请查看这些其他模式的解释计划,看看它是否对您有所帮助。

【讨论】:

  • 非常感谢@RolandoMySQLDBA。我现在已经发布了更新。
  • 我忘了说:请使用+office而不是office
  • 感谢@RolandoMySQLDBA。我已经看到+office 更快,但我的用户并不总是使用它。基本上,他们有一个文本字段(如 Google),可以在其中搜索文档中的内容。
猜你喜欢
  • 2011-12-14
  • 2023-03-04
  • 1970-01-01
  • 2017-11-25
  • 1970-01-01
  • 2017-01-20
  • 2015-06-23
  • 2020-09-09
  • 1970-01-01
相关资源
最近更新 更多