我需要一个更快的解决方案来在 table 上执行 Select,在 1 对 n 相关 table 中具有多个特定匹配
I need a faster solution to perform a Select on a table with multiple specific matchings in a 1-to-n related table
概述
我在系统中有以下结构,用户可以在其中保存作业的属性,他可以自己命名。一个属性可以有 2 个可能的值,一个用户值和一个系统值。
我正在尝试根据属性过滤作业,用户值或系统值都可以命中。
现状
table 职位
id
company_id
status
1
2
active
2
2
created
3
3
created
4
12
inactive
table job_data
job_id
field
value_user
value_xml
1
city
Berlin
1
phone
1234567
1
type
fulltime
2
city
New York
2
phone
33333333
2
type
parttime
3
city
Berlin
3
phone
123
3
type
fulltime
索引:
Table
Key_name
Column_name
Collation
jobs
PRIMARY
id
A
job_data
job_data_jobs_id_foreign
job_id
A
用户现在必须过滤多个属性,例如:“向我显示城市柏林和 phone 123 的所有工作”。这将显示工作 1 和 3,因为它们都在其中一个城市值中有柏林,并且在 phone 值之一中有一个 phone 数字,如 123。
我有几个可行的解决方案,但现在我们的数据库中有 120,000 个活动作业,具有超过一百万个属性,而且我们的代码速度不够快。这是我们目前的解决方案:
SELECT * FROM jobs
WHERE
(
SELECT count(*) FROM job_data
WHERE job_data.job_id = jobs.id
AND job_data.field = "city" AND (job_data.value_xml LIKE "%Martinhaven%" OR job_data.value_user LIKE "%Martinhaven%")
) > 0
AND
(
SELECT count(*) FROM job_data
WHERE job_data.jobposting_id = jobs.id
AND job_data.field = "category" AND (job_data.value_xml LIKE "%omnis%" OR job_data.value_user LIKE "%omnis%")
) > 0;
输出:
id
company_id
status
1
2
active
3
3
created
这已被简化,我们有大约 6 种过滤器可能性,但它们都是同一类,所以我只发布了其中的 2 种。
问题
谁能告诉我,我怎样才能更快地做到这一点?我们目前需要 5-10 秒来完成一个 select。 Ofc 我们可以重组数据库以使其过滤速度更快,但我们仍在努力防止这种情况发生,因为它是一个更大的系统正在建设中。
非常感谢。
不使用 1 个针对每个条件和 table jobs
的每一行进行聚合的相关子查询,而是使用一次条件聚合:
SELECT job_id
FROM job_data
GROUP BY job_id
HAVING SUM(field = 'city' AND (value_xml LIKE '%Martinhaven%' OR value_user LIKE '%Martinhaven%')) > 0
AND SUM(field = 'category' AND (value_xml LIKE '%omnis%' OR value_user LIKE '%omnis%')) > 0
AND .....
获取您想要的所有 job_id
并将它们与运算符 IN:
一起使用
SELECT * FROM jobs
WHERE job_id IN (
SELECT job_id
FROM job_data
GROUP BY job_id
HAVING SUM(field = 'city' AND (value_xml LIKE '%Martinhaven%' OR value_user
LIKE '%Martinhaven%')) > 0
AND SUM(field = 'category' AND (value_xml LIKE '%omnis%' OR value_user
LIKE '%omnis%')) > 0
AND .....
);
如果子查询返回的job_id
个数小我相信这个查询的性能会更好
可以从代码中删除所有 > 0
不等式,但为了清楚起见,我将它们留在那里。
你有两个问题。
- 多个值字段。
like '%word%'
很难索引。
修复架构
将 job_data
更改为单个值和来源。
job_id field value source
1 city Berlin user
3 city Berlin xml
-- Add value and source columns, nullable for now.
alter table job_data add value varchar(255), add source varchar(255);
-- If value_user is not null or blank, add it to value with a source of user.
update job_data
set value = value_user, source = 'user'
where coalesce(value_user, '') <> '';
-- Same for value_xml, source of xml.
update job_data
set value = value_xml, source = 'xml'
where value_xml is not null;
-- Drop the old value columns.
alter table job_data
drop value_xml, drop value_user;
现在我们可以对值和源强制执行 not null 以避免错误数据。
alter table job_data
modify value varchar(255) not null,
modify source varchar(255) not null;
我们还可以强制一个作业的每个字段只能有一个值。或按字段和来源。
-- If a job can have multiple sources for a field.
alter table add unique(job_id, field, source);
-- If it cannot.
alter table add unique(job_id, field);
如果您需要保持兼容性,请提出意见。
create view old_job_data as
select
job_id,
field,
case source when 'user' then value end as value_user,
case source when 'xml' then value end as value_xml
from job_data;
这解决了一些问题。
- 您可以拥有任意数量的来源。
- 您可以确保该值不为空。
- 您可以避免重复。
- 您不需要检查两个地方的值。
- 索引更容易。
查询
有了这个固定,查询就简单多了。
要查找哪些工作匹配两个 field/value 对,通常您会执行 intersect
。但是MySQL没有intersect
,所以我们用自连接来模拟。
select * from jobs
where job_id in (
select distinct j1.job_id
from job_data j1
inner join job_data j2 on j1.job_id = j2.job_id
where
(j1.field = 'city' and j1.value like '%York%')
and
(j2.field = 'type' and j2.value like '%time%')
)
你 没有 来改变 table,你可以 j1.field = 'city' and (j1.value_xml like '%York%' or j1.value_user like '%York%')
,但这样更容易。
索引
在MySQL、like '%word%'
will not use a simple index. It has to scan each row with a matching field. Simple indexes only work for like 'word%'
. Other databases have special indexes for this。我不知道 MySQL.
的解决方案
至少索引 field
这样您至少可以快速找到匹配 field
的行。
create index job_data_field_idx on job_data(field);
现在不再扫描每一行,它只会扫描与该字段匹配的行。
确定您是否真的需要完整的通配符搜索。或者您是否需要在插入数据之前清理数据?如果你能摆脱通配符索引,这个索引将使搜索几乎是即时的。
create index job_data_field_vaue_idx on job_data(value, field);
当使用带有 like '%...' 的不可压缩标准时,这没有太多帮助,但是你不必要地燃烧 CPU 和 IO对每个搜索条件执行 count(*)
。
可能优化器可以发现这一点并在内部将其优化为存在操作,但最好是显式
select *
from jobs j
where exists (
select 1 job_data d
where d.job_id = j.id
and d.field = "city" and (d.value_xml like "%Martinhaven%" or d.value_user like "%Martinhaven%")
)
对于实体属性模型,如果所有值都在同一列中,效率会更高。最初实施的更改可能太大,因此您还应该尝试使用多个条件分别检查每一列,而不是使用 或 。这对于每个索引(在 字段,Value_xml 和 字段,value_user)是最有益的。 =13=]
select *
from jobs j
where exists (select 1 job_data d where d.job_id = j.id and d.field = "city" and d.value_xml like "%Martinhaven%")
union
select *
from jobs j
where exists (select 1 job_data d where d.job_id = j.id and d.field = "city" and d.value_user like "%Martinhaven%")
您可能会发现这会产生更好的性能,因为每个索引都可以使用索引来查找 city 行,然后扫描直到找到匹配项;充其量它几乎可以立即存在,最坏的情况下它并不比现有的 table 扫描差。
部分解:
而不是
( SELECT COUNT(*) ... ) > 0 )
写入
EXISTS ( SELECT 1 ... )
后者会在第一次出现时停止扫描。 (前者要看整个table。)
真正的反派是
OR
-- 优化不好
LIKE '%...'
-- 'leading wildcard' 阻止使用任何索引。
请提供 SHOW CREATE TABLE
以便我们查看存在哪些索引。
另一个问题是笨拙且低效的“实体-属性-值”模式模式的使用。
我担心“XML”。
现在来说说更鼓舞人心的事情。在包含要搜索的数据的列上设置 FULLEXT
,再加上 MATCH(col) AGAINST('+Martinhaven +omnis' IN BOOLEAN MODE)
会非常快。
概述
我在系统中有以下结构,用户可以在其中保存作业的属性,他可以自己命名。一个属性可以有 2 个可能的值,一个用户值和一个系统值。 我正在尝试根据属性过滤作业,用户值或系统值都可以命中。
现状
table 职位
id | company_id | status |
---|---|---|
1 | 2 | active |
2 | 2 | created |
3 | 3 | created |
4 | 12 | inactive |
table job_data
job_id | field | value_user | value_xml |
---|---|---|---|
1 | city | Berlin | |
1 | phone | 1234567 | |
1 | type | fulltime | |
2 | city | New York | |
2 | phone | 33333333 | |
2 | type | parttime | |
3 | city | Berlin | |
3 | phone | 123 | |
3 | type | fulltime |
索引:
Table | Key_name | Column_name | Collation |
---|---|---|---|
jobs | PRIMARY | id | A |
job_data | job_data_jobs_id_foreign | job_id | A |
用户现在必须过滤多个属性,例如:“向我显示城市柏林和 phone 123 的所有工作”。这将显示工作 1 和 3,因为它们都在其中一个城市值中有柏林,并且在 phone 值之一中有一个 phone 数字,如 123。
我有几个可行的解决方案,但现在我们的数据库中有 120,000 个活动作业,具有超过一百万个属性,而且我们的代码速度不够快。这是我们目前的解决方案:
SELECT * FROM jobs
WHERE
(
SELECT count(*) FROM job_data
WHERE job_data.job_id = jobs.id
AND job_data.field = "city" AND (job_data.value_xml LIKE "%Martinhaven%" OR job_data.value_user LIKE "%Martinhaven%")
) > 0
AND
(
SELECT count(*) FROM job_data
WHERE job_data.jobposting_id = jobs.id
AND job_data.field = "category" AND (job_data.value_xml LIKE "%omnis%" OR job_data.value_user LIKE "%omnis%")
) > 0;
输出:
id | company_id | status |
---|---|---|
1 | 2 | active |
3 | 3 | created |
这已被简化,我们有大约 6 种过滤器可能性,但它们都是同一类,所以我只发布了其中的 2 种。
问题
谁能告诉我,我怎样才能更快地做到这一点?我们目前需要 5-10 秒来完成一个 select。 Ofc 我们可以重组数据库以使其过滤速度更快,但我们仍在努力防止这种情况发生,因为它是一个更大的系统正在建设中。
非常感谢。
不使用 1 个针对每个条件和 table jobs
的每一行进行聚合的相关子查询,而是使用一次条件聚合:
SELECT job_id
FROM job_data
GROUP BY job_id
HAVING SUM(field = 'city' AND (value_xml LIKE '%Martinhaven%' OR value_user LIKE '%Martinhaven%')) > 0
AND SUM(field = 'category' AND (value_xml LIKE '%omnis%' OR value_user LIKE '%omnis%')) > 0
AND .....
获取您想要的所有 job_id
并将它们与运算符 IN:
SELECT * FROM jobs
WHERE job_id IN (
SELECT job_id
FROM job_data
GROUP BY job_id
HAVING SUM(field = 'city' AND (value_xml LIKE '%Martinhaven%' OR value_user
LIKE '%Martinhaven%')) > 0
AND SUM(field = 'category' AND (value_xml LIKE '%omnis%' OR value_user
LIKE '%omnis%')) > 0
AND .....
);
如果子查询返回的job_id
个数小我相信这个查询的性能会更好
可以从代码中删除所有 > 0
不等式,但为了清楚起见,我将它们留在那里。
你有两个问题。
- 多个值字段。
like '%word%'
很难索引。
修复架构
将 job_data
更改为单个值和来源。
job_id field value source
1 city Berlin user
3 city Berlin xml
-- Add value and source columns, nullable for now.
alter table job_data add value varchar(255), add source varchar(255);
-- If value_user is not null or blank, add it to value with a source of user.
update job_data
set value = value_user, source = 'user'
where coalesce(value_user, '') <> '';
-- Same for value_xml, source of xml.
update job_data
set value = value_xml, source = 'xml'
where value_xml is not null;
-- Drop the old value columns.
alter table job_data
drop value_xml, drop value_user;
现在我们可以对值和源强制执行 not null 以避免错误数据。
alter table job_data
modify value varchar(255) not null,
modify source varchar(255) not null;
我们还可以强制一个作业的每个字段只能有一个值。或按字段和来源。
-- If a job can have multiple sources for a field.
alter table add unique(job_id, field, source);
-- If it cannot.
alter table add unique(job_id, field);
如果您需要保持兼容性,请提出意见。
create view old_job_data as
select
job_id,
field,
case source when 'user' then value end as value_user,
case source when 'xml' then value end as value_xml
from job_data;
这解决了一些问题。
- 您可以拥有任意数量的来源。
- 您可以确保该值不为空。
- 您可以避免重复。
- 您不需要检查两个地方的值。
- 索引更容易。
查询
有了这个固定,查询就简单多了。
要查找哪些工作匹配两个 field/value 对,通常您会执行 intersect
。但是MySQL没有intersect
,所以我们用自连接来模拟。
select * from jobs
where job_id in (
select distinct j1.job_id
from job_data j1
inner join job_data j2 on j1.job_id = j2.job_id
where
(j1.field = 'city' and j1.value like '%York%')
and
(j2.field = 'type' and j2.value like '%time%')
)
你 没有 来改变 table,你可以 j1.field = 'city' and (j1.value_xml like '%York%' or j1.value_user like '%York%')
,但这样更容易。
索引
在MySQL、like '%word%'
will not use a simple index. It has to scan each row with a matching field. Simple indexes only work for like 'word%'
. Other databases have special indexes for this。我不知道 MySQL.
至少索引 field
这样您至少可以快速找到匹配 field
的行。
create index job_data_field_idx on job_data(field);
现在不再扫描每一行,它只会扫描与该字段匹配的行。
确定您是否真的需要完整的通配符搜索。或者您是否需要在插入数据之前清理数据?如果你能摆脱通配符索引,这个索引将使搜索几乎是即时的。
create index job_data_field_vaue_idx on job_data(value, field);
当使用带有 like '%...' 的不可压缩标准时,这没有太多帮助,但是你不必要地燃烧 CPU 和 IO对每个搜索条件执行 count(*)
。
可能优化器可以发现这一点并在内部将其优化为存在操作,但最好是显式
select *
from jobs j
where exists (
select 1 job_data d
where d.job_id = j.id
and d.field = "city" and (d.value_xml like "%Martinhaven%" or d.value_user like "%Martinhaven%")
)
对于实体属性模型,如果所有值都在同一列中,效率会更高。最初实施的更改可能太大,因此您还应该尝试使用多个条件分别检查每一列,而不是使用 或 。这对于每个索引(在 字段,Value_xml 和 字段,value_user)是最有益的。 =13=]
select *
from jobs j
where exists (select 1 job_data d where d.job_id = j.id and d.field = "city" and d.value_xml like "%Martinhaven%")
union
select *
from jobs j
where exists (select 1 job_data d where d.job_id = j.id and d.field = "city" and d.value_user like "%Martinhaven%")
您可能会发现这会产生更好的性能,因为每个索引都可以使用索引来查找 city 行,然后扫描直到找到匹配项;充其量它几乎可以立即存在,最坏的情况下它并不比现有的 table 扫描差。
部分解:
而不是
( SELECT COUNT(*) ... ) > 0 )
写入
EXISTS ( SELECT 1 ... )
后者会在第一次出现时停止扫描。 (前者要看整个table。)
真正的反派是
OR
-- 优化不好LIKE '%...'
-- 'leading wildcard' 阻止使用任何索引。
请提供 SHOW CREATE TABLE
以便我们查看存在哪些索引。
另一个问题是笨拙且低效的“实体-属性-值”模式模式的使用。
我担心“XML”。
现在来说说更鼓舞人心的事情。在包含要搜索的数据的列上设置 FULLEXT
,再加上 MATCH(col) AGAINST('+Martinhaven +omnis' IN BOOLEAN MODE)
会非常快。