大表,慢查询

时间:2018-08-22 20:54:27

标签: mysql sql

这是针对LAMP项目的。为了说明的目的,我将使用一个简化的问题:

create table table1 (
  id int unsigned primary key,
  mail_zip varchar(9),
  index (mail_zip(5))
);

create table table2 (
  name varchar(255),
  zip varchar(5)
);

select table1.id from table1
where substring(mail_zip, 1, 5) in
  (select zip from table2 where name = 'test');

表1包含5百万条带有9位邮政编码的记录。对于特定的table2.name,表2通常少于10行,并且仅使用5位邮政编码。该查询花费了无法接受的长时间。在我的实际代码中,table1是包含100多个列的国家数据库的副本。我想尝试保持该表与国家/地区数据库之间的对等,以便避免添加列或将zip缩短至5位数字;但是,我的默认计划是将mail_zip限制为插入时的前5位,以避免使用substring(),除非有人有更好的主意,否则我认为这是个问题。

编辑:不幸的是,下面的大多数建议除了粘性位之外,没有带来任何明显的改进。我的查询最初写的时间超过3分钟。其他大多数建议也是如此。粘性位将时间缩短到3.5秒。将table1中的mail_zip字段截断为5位数字可将查询时间降至0.06sec。虽然我希望本地表与国家数据库完全匹配,但是我很难看到应用程序中任何实际的功能丢失,只需删除邮政编码的最后4位,这就是我要走的路。 >

4 个答案:

答案 0 :(得分:2)

问题出在过滤器的“左侧表达式”上。

  

...中的子字符串(mail_zip,1,5)...

通常,等式左侧的表达式可以/将破坏索引的使用。典型的解决方案是重新定义查询,但您不能这样做。那个简单的解决方案还没有出现。

尽管如此,如果您正在运行MySQL 5.7或更高版本,则有一种非常快的解决方法:

  1. 向表中添加一个虚拟列,以计算5位zip值。

  2. 在虚拟列上创建索引。

  3. 修改查询以使用虚拟列而不是原始列。

这里是例子:

alter table table1 add zip5 varchar(5) 
  generated always as (substring(mail_zip, 1, 5)) virtual;

create index ix1_table1 on table1 (zip5);

select table1.id from table1
where zip5 in
  (select zip from table2 where name = 'test');

答案 1 :(得分:0)

您可以尝试将其重写为

select table1.id 
  from table1 t1
 where exists
       ( SELECT 1
           FROM table2 t2
          WHERE substring(t1.mail_zip,1,5) = t2.zip
            AND t2.zip
       );

将其写为存在或联接实际上可能会使用该索引。

通常来说,如果您必须在条件下执行功能

例如substring(t1.mail_zip,1,5)= t2.zip

表明您的模型有改进的空间。

答案 2 :(得分:0)

尝试

select table1.id from table1
INNER JOIN table2
ON table1.mail_zip LIKE CONCAT(table2.zip,'%')
WHERE name = 'test';

答案 3 :(得分:0)

您可以尝试将INNER JOINLIKE一起使用。

SELECT DISTINCT
       table1.id
       FROM table1
            INNER JOIN table2
                       ON table1.mail_zip LIKE concat(table2.zip, '%')
            WHERE table2.name = 'test';

这会将功能的使用转移到较小表的列上。

为此,还请在table1 (mail_zip, id)上创建一个复合索引(不要限制mail_zip)。

CREATE INDEX table1_mail_zip_id
             ON table1
                (mail_zip,
                 id);

也许还有table2 (name, zip)上的另一个索引。尽管我想如果table2中只有10行,那不会有什么大不同。

CREATE INDEX table2_name_zip
             ON table2
                (name,
                 zip);

就像table1上的索引可能会被拾取(在我的测试中确实如此,但是我没有数据,所以说的不多)。尽管DISTINCT会受到惩罚,但是我希望索引的使用会大大超过该索引。