优化 MySQL Query - 多列搜索条件

Posted

技术标签:

【中文标题】优化 MySQL Query - 多列搜索条件【英文标题】:Optimizing MySQL Query - search condition on multiple columns 【发布时间】:2020-01-25 19:52:41 【问题描述】:

我使用的是 mysql 5.7.25,这是我要优化的查询:

SELECT a.contract,
       a.phone_number_1,
       a.phone_number_2,
       a.phone_number_3,
       a.phone_number_4,
       a.phone_number_5
  FROM tempdb.customer_crm a
 WHERE CHAR_LENGTH(a.contract) = 12
   AND (
         a.contract in (SELECT contract_final FROM tempdb.relevant_contracts)
         OR a.phone_number_1 in (SELECT phone_number FROM tempdb.relevant_numbers_1)
         OR a.phone_number_2 in (SELECT phone_number FROM tempdb.relevant_numbers_2)
         OR a.phone_number_3 in (SELECT phone_number FROM tempdb.relevant_numbers_3)
         OR a.phone_number_4 in (SELECT phone_number FROM tempdb.relevant_numbers_4)
         OR a.phone_number_5 in (SELECT phone_number FROM tempdb.relevant_numbers_5)
       );

customer_crm 表在 5 列中有 5 个不同的电话号码。我需要过滤表 relevant_numbers 中存在 5 个电话号码中的任何一个的所有记录。我制作了 5 个表 relevant_numbers 副本,因为我只能使用 TEMPORARY 表(在 MySQL 中不能多次打开)。中的记录数:

customer_crm:8000 万 relevant_numbers: 63 千 relevant_contracts:9.3 万 查询结果:10万

此查询耗时过长。我已经使用(电话号码长度条件)节省了几分钟:

SELECT a.contract,
       a.phone_number_1,
       a.phone_number_2,
       a.phone_number_3,
       a.phone_number_4,
       a.phone_number_5
  FROM tempdb.customer_crm a
 WHERE CHAR_LENGTH(a.contract) = 12
   AND (
         a.contract in (SELECT contract_final FROM tempdb.relevant_contracts)
         OR (CHAR_LENGTH(a.phone_number_1) > 9 AND a.phone_number_1 in (SELECT phone_number FROM tempdb.relevant_numbers_1))
         OR (CHAR_LENGTH(a.phone_number_2) > 9 AND a.phone_number_2 in (SELECT phone_number FROM tempdb.relevant_numbers_2))
         OR (CHAR_LENGTH(a.phone_number_3) > 9 AND a.phone_number_3 in (SELECT phone_number FROM tempdb.relevant_numbers_3))
         OR (CHAR_LENGTH(a.phone_number_4) > 9 AND a.phone_number_4 in (SELECT phone_number FROM tempdb.relevant_numbers_4))
         OR (CHAR_LENGTH(a.phone_number_5) > 9 AND a.phone_number_5 in (SELECT phone_number FROM tempdb.relevant_numbers_5))
       );

仍然需要大约 10 分钟。我尝试使用 EXISTS 条件而不是 IN 并且它需要更长的时间。我也尝试过使用左连接,这也需要更长的时间。所有列都单独编入索引。

任何帮助将不胜感激。谢谢。

【问题讨论】:

为什么只能使用临时表?为什么不使用连接?表上存在哪些索引? 【参考方案1】:

customer_crm 表在 5 列中有 5 个不同的电话号码。我需要过滤表相关号码中存在 5 个电话号码中的任何一个的所有记录。

与其单独检查relevant_numbers 中的每个电话号码,不如将existsin 条件一起使用?

select c.*
from tempdb.customer_crm c
where 
    exists (
        select 1
        from tempdb.relevant_contracts o
        where o.contract_final = c.contract 
    )
    or exists (
        select 1
        from tempdb.relevant_numbers n
        where n.phone_number in (
            c.phone_number_1,
            c.phone_number_2,
            c.phone_number_3,
            c.phone_number_4,
            c.phone_number_5
        )
    )

为了性能,您可以尝试以下索引:

customer_crm(
    contract, 
    phone_number_1,
    phone_number_2,
    phone_number_3,
    phone_number_4,
    phone_number_5
)
relevant_contracts(contract_final)
relevant_numbers (phone_number)

我也不确定检查contract 的长度是否有益:在此处使用函数会使查询不可搜索(即阻止使用索引)。

【讨论】:

合同长度是一项要求。我在电话号码上添加了可选的长度检查,它似乎减少了时间。让我试试你的建议然后回来。此外,它应该是“OR EXISTS”而不是 AND。对吗? @Imtiaz:contract 列的数据类型是什么:字符串还是数字? 合约是 varchar(20),所有的电话号码也是。我无法更改原始表格,因为它们是客户的。但是,我可以更改临时表中的类型。 感谢@GMB 的帮助。我尝试了您的建议,但查询执行时间保持不变。【参考方案2】:

OR 是性能杀手。 IN ( SELECT ... )也是如此。

目前的查询将对 80M 行进行全表扫描,并查找临时表。如果您努力为您的临时表建立索引,那么这些辅助查找将只有 1 行,否则为 63K 行——这将增加 25 trillion 次查找。今年可能会完结。

A计划:OR变成UNION

    (  SELECT  cc.id
            FROM  tempdb.customer_crm AS cc
            JOIN  tempdb.relevant_contracts AS rc
            WHERE  cc.contract = rc.contract 
    )  UNION  
    (  SELECT  cc.id
            FROM  tempdb.customer_crm AS cc
            JOIN  tempdb.relevant_numbers_1 AS rn
            WHERE  cc.phone_number_1 = rn.phone_number 
    )  UNION
    (  SELECT  cc.id
            FROM  tempdb.customer_crm AS cc
            JOIN  tempdb.relevant_numbers_2 AS rn
            WHERE  cc.phone_number_2 = rn.phone_number 
    )  UNION
    (  SELECT  cc.id
            FROM  tempdb.customer_crm AS cc
            JOIN  tempdb.relevant_numbers_3 AS rn
            WHERE  cc.phone_number_3 = rn.phone_number 
    )  UNION  
    (  SELECT  cc.id
            FROM  tempdb.customer_crm AS cc
            JOIN  tempdb.relevant_numbers_4 AS rn
            WHERE  cc.phone_number_4 = rn.phone_number 
    )  UNION  
    (  SELECT  cc.id
            FROM  tempdb.customer_crm AS cc
            JOIN  tempdb.relevant_numbers_5 AS rn
            WHERE  cc.phone_number_5 = rn.phone_number 
    )

我假设idcustomer_crmPRIMARY KEY。您将需要customer_crm 上的这些索引:

INDEX(contract, id)
INDEX(phone_number_1, id)
INDEX(phone_number_2, id)
INDEX(phone_number_3, id)
INDEX(phone_number_4, id)
INDEX(phone_number_5, id)

使用上面的查询作为子查询,JOIN 返回到customer_crm 以获取您真正需要的任何列。

这将是大约 100 万次操作 - 要少得多

长度=12 的检查可能会在以后作为一个小麻烦。

B 计划:不要使用 5 列。

将一组事物分布在多个列中或打包在一个列中通常是一种糟糕的架构设计。相反,让另一个表(至少)2 列:numberid 加入回主表。

使用INDEX(number),它有 5*80M 行并不重要。

计划 C:您是否愿意在创建临时表之前进行备份?其他优化也是可能的。

【讨论】:

谢谢瑞克。你的 A 计划创造了奇迹。查询执行时间减少了大约 6 倍。由于这是客户端数据库,我无法实施 B 或 C 计划。我接受它作为解决方案。

以上是关于优化 MySQL Query - 多列搜索条件的主要内容,如果未能解决你的问题,请参考以下文章

MySQL优化 优化诀窍

MySQL优化之慢日志分析(Anemometer+Pt-query-digest)

Mysql之索引优化

MySQL 查询语句优化思路

mysql单列索引多列索引的使用

Mysql 优化