SQL IN语句缓慢

时间:2014-09-03 02:01:58

标签: mysql sql

我在使用IN语句高效运行SQL时遇到了一些麻烦。如果我单独运行这两个语句,并手动粘贴一系列结果(在这种情况下有30个vendor_id),vendor_master查询立即运行,发票查询在大约2秒内运行。

select * FROM invoices where vendor_id IN

(

select vendor_id from vendor_master WHERE vendor_master_id = 12345

);

那么是什么导致了巨大的减速,超过60秒并经常超时?有没有办法用逗号将结果放在变量中?或者让内部语句执行第一个?

4 个答案:

答案 0 :(得分:3)

在MySQL 5.6.6之前,in的优化效率非常低。请改用exists

select *
FROM invoices i
where exists (select 1
              from vendor_master vm
              where i.vendor_id = vm.vendor_id and vm.vendor_master_id = 12345
             );

为获得最佳效果,您需要vendor_master(vendor_id, vendor_master_id)上的索引。

答案 1 :(得分:1)

您可以尝试使用INNER JOIN

select i.* 
FROM invoices i
INNER JOIN vendor_master vm 
        ON i.vendor_id = vm.vendor_id AND vm.vendor_master_id = 12345

答案 2 :(得分:1)

您可以JOIN使用DISTINCT代替IN

SELECT *
FROM invoices JOIN 
(
    SELECT DISTINCT vendor_id as vid
    FROM vendor_master
    WHERE vendor_master_id = 12345
) vmi
ON invoices.vendor_in = vmi.vid

请记住,您必须拥有DISTINCT,否则如果内部查询有两条记录,那么您将在JOIN之后重复行,结果将不同于{{ 1}}查询。

答案 3 :(得分:1)

  

那么是什么导致巨大的减速,超过60秒而且经常超时?

IN条件内的数据集“小”和“确定”时,IN子句运行良好。那是因为每行一次评估条件。因此,假设IN子句中的查询返回100行而FROM子句中的表有1000行,则服务器必须执行100 * 1000 = 100,000比较以过滤掉您的数据。过滤太少的数据太费力了,你不觉得吗?当然,如果您的数据集(fromin条款中的数据集都较大),您可以想象效果。

顺便说一下,当您使用子查询作为in条件时,还会产生额外的开销:子查询需要为每一行执行一次。所以序列是这样的:

  • 第1行
    • 执行子查询
    • 检查第1行的值是否与子查询的结果值匹配
    • 如果上述情况属实,请将行保留在结果集中;除此之外
  • 第2行
    • 执行子查询
    • 检查第2行的值是否与子查询的结果值匹配
    • 如果上述情况属实,请将行保留在结果集中;除此之外
  • ...

要做太多工作,你不觉得吗?


  

有没有办法用逗号将结果放在变量中?

是的,有办法...但你真的想要这样做吗?我们来看看:

首先,创建一个包含您要过滤的值的列表:

set @valueList = (select group_concat(vendor_id separator ',')
                 from (select vendor_id from vendor_master where vendor_master_id = 12345) as a)

然后,创建一个SQL表达式:

set @sql = concat('select * from invoices where vendor_id in (', @valueList, ')';

最后,创建一个准备好的语句并执行它:

prepare stmt from @sql;
execute stmt;
-- when you're done, don't forget to deallocate the statement:
-- deallocate prepare stmt;

我再次问你:你真的想要做这一切吗?


  

或者先让内部语句执行?

所有其他答案都指向了正确的方向:而不是使用in使用inner join

select i.*
from invoices as i
     inner join (
         select distinct vendor_id 
         from vendor_master
         where vendor_master_id = 12345
     ) as vm on i.vendor_id = vm.vendor_id;

如果由于某种原因,这仍然太慢,我想到的唯一选择是:创建一个临时表(一种“分而治之的策略”):

drop table if exists temp_vm;
create temporary table temp_vm
    select distinct vendor_id
    from vendor_master
    where vendor_master_id = 12345;
alter table temp_vm
    add index vi(vendor_id);
select i.*
from invoices as i inner join temp_vm as vm on i.vendor_id = vm.vendor_id;

请记住:临时表仅对创建它们的连接可见,并在连接关闭或终止时被删除。


在任何情况下,如果您确保表格已正确编入索引,您的表现将会得到改善;具体而言,您需要确保将invoices.vendor_id和vendor_master.vendor_master_id`编入索引。