【问题标题】:MySQL Dynamic Optimization with variable AND OR NOT operatorsMySQL 动态优化与变量 AND OR NOT 运算符
【发布时间】:2022-08-15 03:05:59
【问题描述】:

更新 - 此问题的最终解决方案

我们的动态系统允许对姓名、职位、电话号码等内容进行布尔插值匹配。所以我们可以说:

姓名(\"ted\" OR \"mike\" OR \"david\" AND \"martin\") AND Title(\"developer\" AND \"senior\" NOT \"CTO) AND City(\"圣地亚哥\”)

完成此操作的方法是遵循以下动态创建的分组示例。它非常简单,但是必须使用 HAVING COUNT 才能正确定义 AND 索引。

在此示例中,access_indexes 也不是帐户有权访问的 ID 索引列表,因此如果“搜索”返回帐户无法访问的人,则不会显示。

感谢大家的帮助,尤其是@BillKarwin!

WITH filter0 AS
(
    SELECT pm.ID FROM person_main pm
    WHERE MATCH(pm.name_full) AGAINST (\'(ted)\' IN BOOLEAN MODE)
), 
filter1 AS
(
    SELECT ram.object_ref_id AS ID
    FROM ras_assignment_main ram
    WHERE ram.object_type_c = 1
        AND ram.assignment_type_c = 1
        AND ram.assignment_ref_id IN (2)
), 
persongroup0_and AS 
( 

    SELECT pg0_a.ID FROM
    (
        SELECT ID FROM filter0            
    ) pg0_a
    GROUP BY pg0_a.ID
    HAVING COUNT(pg0_a.ID) = 1
),
persongroup0 AS 
(
    SELECT pm.ID
    FROM person_main pm 
    JOIN persongroup0_and pg0_and ON pm.ID = pg0_and.ID 
),
persongroup1_and AS 
( 
    SELECT pg1_a.ID FROM
    (
        SELECT ID FROM filter1            
    ) pg1_a
    GROUP BY pg1_a.ID
    HAVING COUNT(pg1_a.ID) = 1
),  
persongroup1 AS 
(
    SELECT pm.ID
    FROM person_main pm 
    JOIN persongroup1_and pg1_and ON pm.ID = pg1_and.ID
),  
person_all_and AS 
(     
    SELECT paa.ID FROM
    (
        SELECT ID FROM persongroup0 
        UNION ALL (SELECT ID FROM persongroup1)             
    ) paa
    GROUP BY paa.ID
    HAVING COUNT(paa.ID) = 2
), 
person_all AS 
(
    SELECT pm.ID
    FROM person_main pm 
    JOIN person_all_and pa_and ON pm.ID = pa_and.ID 
),
person_access AS
(
    SELECT pa.ID
    FROM person_all pa
    LEFT JOIN access_indexes ai ON pa.ID = ai.ID
)
SELECT (JSON_ARRAYAGG(pm.ID))
FROM 
(        
    SELECT person_sort.ID
    FROM
    (
        SELECT pa.ID
        FROM person_access pa
        GROUP BY pa.ID
    ) person_sort    
) pm;

我们的前端系统能够使用 AND/OR/NOT 从多个表中定义动态 SQL 查询,并且核心系统工作正常 - 但由于对 IN 的复合扫描,它变得不可用。对于我的生活,我无法弄清楚如何在不使用 IN 的情况下拥有这种级别的动态功能。下面是运行良好的代码(过滤器匹配速度超快),但 IN 扫描的复合需要 > 60 秒,因为它有 50,000 多条记录用于某些过滤器返回。


        WITH filter0 AS
        (
            SELECT pm.ID FROM person_main pm
            WHERE MATCH(pm.name_full) AGAINST (\'mike meyers\' IN BOOLEAN MODE)
        ), 
        filter1 AS
        (
            SELECT phw.person_main_ref_id AS ID
            FROM person_history_work phw
            WHERE MATCH(phw.work_title) AGAINST(\'developer\' IN BOOLEAN MODE)                
        ), 
        filter2 AS
        (
            SELECT pa.person_main_ref_id AS ID
            FROM person_address pa
            WHERE pa.global_address_details_ref_id IN
            (
                SELECT gad.ID
                FROM global_address_details gad
                WHERE gad.address_city LIKE \'%seattle%\'
            )
        ),        
        all_indexes AS
        (         
            SELECT ID FROM filter0
            UNION (SELECT ID FROM filter1)
            UNION (SELECT ID FROM filter2)
        ),         
        person_filter AS
        (
            SELECT ai.ID
            FROM all_indexes ai
            WHERE 
            ( 
                ai.ID IN            (SELECT ID FROM filter0) 
                AND ai.ID NOT IN    (SELECT ID FROM filter1) 
                OR ai.ID IN         (SELECT ID FROM filter2)
            ) 
        )
         
        SELECT (JSON_ARRAYAGG(pf.ID)) FROM person_filter pf;

过滤器 0 有 461 条记录,过滤器 1 有 48480 条,过滤器 2 有 750 条。

关键问题在于 WHERE 语句;因为前端可以在任何“加入”查询中说 AND/OR 而不是。

因此,如果我将其更改为:

                ai.ID IN      (SELECT ID FROM filter0) 
                AND ai.ID IN  (SELECT ID FROM filter1) 
                AND ai.ID IN  (SELECT ID FROM filter2)

查询时间超过 60 秒。因为它正在扫描 461 * 48480 * 750 = 16,761,960,00。啊。

当然,如果它是静态存储过程或调用,我可以对此进行硬编码,但它是一个动态插值系统,采用用户定义的设置,因此用户可以定义上述内容。

如您所见,我所做的是创建所有涉及的索引的列表,然后根据前端 Web 工具定义的 AND/OR/NOT 值选择它们。

显然 IN 不会为此工作;问题是我可以使用哪些其他不涉及使用 IN 的技术来允许与 AND/OR/NOT 具有相同级别的灵活性?


评论中@BillKarwin 的更新

所以下面的代码非常适合执行 AND、NOT 和 OR:

            SELECT pm.ID
            FROM person_main pm
            JOIN filter0 f0 ON f0.ID = pm.ID -- AND
            LEFT JOIN filter1 f1 ON pm.ID = f1.ID WHERE f1.ID IS NULL -- NOT
            UNION (SELECT ID FROM filter2) -- OR

我相信我可以使用我们的系统完成这项工作;我只需要存储不同的类型(AND/NOT/OR)并在进程中执行它们;让我做一些更新,我会回复你。

  • 很难说,因为我们不知道您的用户定义条件的可能性范围是多少。但总的来说,我建议使用JOIN 作为实现AND 条件的一种方式。然后您可以将 UNION 用于OR 条件。
  • 但归根结底,优化用户提出的所有可能的动态查询实际上是不可能的。
  • @BillKarwin 是的,但是不呢?那是核心问题;我希望我们有办法从单个 ID 的 UNION 中取消值(我认为 MSSQL 有 EXCEPT 或其他东西),但我看不到实现它的动态方法。
  • 正确,MySQL 支持 UNION,但 does not yet support the other operations EXCEPT or INTERSECT。除了使用外连接外,还有其他方法可以模拟。该技术称为排除连接。示例:urgenthomework.com/sql-left-excluding-join-homework-help
  • @BillKarwin 明白了,谢谢 - 确实有效 - 但正如你上面提到的,将所有动态组合与 AND/OR/NOT 匹配似乎是不可能的 - 特别是对于嵌套组并且没有非常具体的限制。例如,如果他们执行 AND/NOT/OR 单独处理,则无法按 UNION、JOIN 或 LEFT JOIN(除外)按接收顺序进行处理;必须编写一种解释器才能正确排序查询,并且分组会造成管理混乱。好奇您建议对前端查询引擎施加哪些限制?

标签: mysql query-optimization dynamic-sql


【解决方案1】:

正如上面的 cmets 中所讨论的:

从逻辑上讲,当您的子查询是表达式的 AND 项时,您可以用 JOIN 替换它们,或者当它们是表达式的 OR 项时,您可以将它们替换为 UNION。还了解排除连接。

但这并不一定意味着查询会运行得更快,除非您创建了索引来支持连接条件和用户定义的条件。

但是您应该创建哪些索引?

最终,不可能优化用户提出的所有动态查询。您也许可以运行他们的查询(正如您已经在做的那样),但它们效率不高。

允许用户指定任意条件是一种失败的游戏。最好给他们一组固定的选择,这些选择是您花时间优化的查询类型。然后让他们运行“用户指定的”查询,但要清楚地标记它没有优化,并且可能需要很长时间。

【讨论】:

  • 修改原始帖子以显示解决方案“风格和技术”。
【解决方案2】:

避免使用IN ( SELECT ... ) -- 使用JOINEXISTS

避免SELECT ID FROM ( SELECT ID FROM .... )——外部 SELECT 是不必要的。

UNION 移到外层(在某些情况下)

all_indexes 似乎简化为

( SELECT phw.person_main_ref_id AS ID
            FROM  person_history_work AS phw
            WHERE  MATCH(phw.work_title) AGAINST('developer' IN BOOLEAN MODE) 
) UNION ALL
( SELECT gad.ID
      FROM  global_address_details AS gad
      WHERE  gad.address_city LIKE '%seattle%' 
) 

可以把最后一部分改成WHERE address_city = 'seattle'吗?如果是这样,那么您可以使用INDEX(address_city) 如果不是,那么 FULLTEXT 索引和 MATCH 是否适合您?

看看您是否可以按照我的指示简化其余部分。

WITH 最近才添加到 MySQL 的语法中。我怀疑它需要再发布一两个版本才能得到很好的优化。尽量避免WITH。由于您正在“构建”查询,因此您可以根据需要“构建”UNIONLEFT JOIN 等。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-17
    • 1970-01-01
    • 2023-01-31
    相关资源
    最近更新 更多