这是针对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位,这就是我要走的路。 >
答案 0 :(得分:2)
问题出在过滤器的“左侧表达式”上。
...中的子字符串(mail_zip,1,5)...
通常,等式左侧的表达式可以/将破坏索引的使用。典型的解决方案是重新定义查询,但您不能这样做。那个简单的解决方案还没有出现。
尽管如此,如果您正在运行MySQL 5.7或更高版本,则有一种非常快的解决方法:
向表中添加一个虚拟列,以计算5位zip值。
在虚拟列上创建索引。
修改查询以使用虚拟列而不是原始列。
这里是例子:
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 JOIN
与LIKE
一起使用。
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
会受到惩罚,但是我希望索引的使用会大大超过该索引。