优化 MySQL“可选”FULLTEXT 多表搜索

Sae*_*eid 6 mysql optimization full-text-search

我需要根据客户输入选择带有optional columnsinwhere子句的数据。我的查询是这样的:

SELECT a.id, a.title, b.txt AS b_txt, c.txt AS c_txt 
FROM a 
LEFT JOIN b ON a.b_id=b.id 
LEFT JOIN c ON a.c_id=c.id 
where a.status=1 
AND (0=@bid OR a.b_id=@bid) 
AND (0=@cid OR a.c_id=@cid) 
Run Code Online (Sandbox Code Playgroud)

@bid并且@cid是客户端输入,如果客户端不提供输入,则这些值必须>0使用默认值0

客户端可以不提供任何输入,因此它将选择status=1忽略b_idc_id列的所有数据

客户可以提供@bid或提供@cid两者,查询将相应地选择。

表引擎是 InnoDB,列有索引,外键和关系集。

到目前为止一切都很好。EXPLAIN SQL显示选择是根据提供的索引完成的。

现在我需要在所有 3 个表上添加全文搜索来查询是我遇到问题的地方。如果客户端提供任何关键字进行搜索,文本搜索也是可选的。

全文索引定义为 a.title、b.txt 和 c.txt

我将查询更改为:

SELECT a.id, a.title, b.txt AS b_txt, c.txt AS c_txt 
FROM a 
LEFT JOIN b ON a.b_id=b.id 
LEFT JOIN c ON a.c_id=c.id 
where a.status=1 
AND ('0'=@keywords OR (MATCH(a.title) AGAINST(@keywords IN BOOLEAN MODE) OR MATCH(b.txt) AGAINST(@keywords IN BOOLEAN MODE) OR MATCH(c.txt) AGAINST(@keywords IN BOOLEAN MODE))) 
AND (0=@bid OR a.b_id=@bid) 
AND (0=@cid OR a.c_id=@cid) 
Run Code Online (Sandbox Code Playgroud)

查询似乎正在返回我想要的结果,但explain query返回type=ALL和全表扫描所以没有查询没有以优化的方式工作。

如果我将OR匹配之间更改为AND然后explain query返回type=fulltext并选择在 FULLTEXT 索引上完成,但我需要OR样式。

我正在考虑加入不同的结果集,但无法找出如何加入,因为输入是可选的,并且可能没有输入,因此不需要进行全文搜索。

任何解决方案?

编辑:

好的,感谢 jkavalik 的评论和 Rick 的回答,看来我需要添加一些说明:

实际上我使用的是 WSO2 数据服务服务器的数据服务。所以我只是将输入参数传递给数据服务,我无法根据用户输入生成选择查询。(有一个选项可以将查询字符串的一部分作为输入参数传递,但出于安全原因我不打算这样做)

所以我有两个选择:

  1. 编写单独的查询,为每种输入参数组合情况接受不同的输入参数集。根据用户输入决定将参数发送到哪个查询。好吧,在实际情况下,可选输入是 10+,这将是很多查询,而且似乎不是正确的解决方案。可选字段可能会随着时间的推移而改变,未来的维护成本很高。看起来不是处理这个问题的标准方法。

  2. 以可以处理可选字段的方式编写单个查询。

我更喜欢选项 2,除非还有我不知道的第三个选项?

在查询被传递到 MySQL 并在 MySQL 中执行之前,@variables它们要么被输入参数的值替换,要么被默认值(0在本例中)替换。实际上它们:variable不是,@variable但我@在本示例中将其更改为更好的阅读或理解。

Ric*_*mes 1

错误的做法。 相反,SELECT根据用户提供的字段“构建”。

为什么? OR优化工作做得很差。相反,如果您从 中删除该子句WHERE,则不需要测试 0,从而避免OR.

更糟糕的是OR跨越多个表,因为您需要进行检查FULLTEXT。就目前而言,运行查询的唯一方法是评估所有表中所有行的所有组合,然后开始过滤。(好吧,我夸大了,但没有夸张太多。)

避免的常见方法OR是将其变成UNION

( SELECT id FROM a WHERE MATCH(a...) ... )
UNION DISTINCT
( SELECT id FROM a JOIN b ON ... WHERE MATCH(b...) ... )
...
Run Code Online (Sandbox Code Playgroud)

这将获得 的所有可能值a.id

为什么LEFT 这会无缘无故地减慢子查询的第二部分和第三部分的速度。消除LEFT。如果没有b行,则相当于MATCH失败。

然后,将其放入子查询中以获取其余所需数据:

SELECT a.id, a.title, b.txt AS b_txt, c.txt AS c_txt 
FROM ( the above "union" query ) AS tmp
JOIN a 
LEFT JOIN b ON a.b_id=b.id 
LEFT JOIN c ON a.c_id=c.id;
Run Code Online (Sandbox Code Playgroud)

在这种情况下,您可能需要LEFT;但如果不这样做,请将其删除。

其他注意事项

当您“构造”查询时,您也可以插入@值。@变量在某些情况下会抑制优化器;实际的文字更容易处理。请务必转义它们以帮助防止“sql 注入”。

如果不明显,0=@bid OR就消失。