Mysql 使用同一表上的多个内部联接优化SQL查询

Mysql 使用同一表上的多个内部联接优化SQL查询,mysql,join,query-performance,entity-attribute-value,Mysql,Join,Query Performance,Entity Attribute Value,我遇到了一个性能问题: 商店有一个物品过滤器,其类别为“颜色”、“大小”、“性别”和“特征”。所有这些详细信息都存储在article\u criterias表中,如下所示: 条款标准的表格布局是;此表约有36000行: article_id | group | option | option_val 100 | "size" | "35" | 35.00 100 | "size" | "36" | 36.00 100

我遇到了一个性能问题:

商店有一个物品过滤器,其类别为“颜色”、“大小”、“性别”和“特征”。所有这些详细信息都存储在
article\u criterias
表中,如下所示:

条款标准的表格布局是;此表约有36000行:

article_id | group    | option | option_val
       100 | "size"   | "35"   |     35.00
       100 | "size"   | "36"   |     36.00
       100 | "size"   | "36½"  |     36.50
       100 | "color"  | "40"   |     40.00
       100 | "color"  | "50"   |     50.00
       100 | "gender" | "1"    |      1.00
       101 | "size"   | "40"   |     40.00
       ...
我们有一个动态构建的SQL查询,它基于当前选择的条件。查询适用于2-3个条件,但当选择5个以上的选项时,查询速度会非常慢(每个额外的内部联接大约会使执行时间加倍)

我们如何使这个SQL更快,甚至用更高性能的概念替换内部连接

这是查询(逻辑正确,只是性能不好):


正如@affan pathan所建议的,添加索引确实解决了问题:

CREATE INDEX text_option 
ON `article_criterias` (`article_id`, `group`, `option`);

CREATE INDEX numeric_option 
ON `article_criterias` (`article_id`, `group`, `option_val`);

这两个索引将上述查询表单的执行时间缩短了近1分钟,缩短到50毫秒以下

正如@affan pathan建议的那样,添加索引确实解决了这个问题:

CREATE INDEX text_option 
ON `article_criterias` (`article_id`, `group`, `option`);

CREATE INDEX numeric_option 
ON `article_criterias` (`article_id`, `group`, `option_val`);

这两个索引将上述查询表单的执行时间缩短了近1分钟,缩短到50毫秒以下

我知道你创建的索引解决了你的问题, 但是,为了使用伪备选方案(避免多个内部联接),您可以尝试这样的方法吗?(我只使用了三个条件进行了测试。您的条件应该插入到内部查询中。要仅选择满足所有条件的记录,您必须更改last WHERE条件(其中max=3,使用您上面编写的条件数;因此,如果您使用的是5个条件,您应该在其中max=5)。(为了便于使用,我更改了列组和选项的名称)。 这只是一个想法,所以请做一些测试,检查性能,请让我知道

CREATE TABLE CRITERIA (ARTICLE_ID INT, GROU VARCHAR(10), OPT VARCHAR(20), OPTION_VAL NUMERIC(12,2));
CREATE TABLE ARTICLES (ID INT);
INSERT INTO CRITERIA VALUES (100,'size','35',35);
INSERT INTO CRITERIA VALUES (100,'size','36',36);
INSERT INTO CRITERIA VALUES (100,'color','40',40);
INSERT INTO CRITERIA VALUES (100,'gender','1',1);
INSERT INTO CRITERIA VALUES (200,'size','36.2',36.2);
INSERT INTO CRITERIA VALUES (300,'size','36.2',36.2);
INSERT INTO ARTICLES VALUES (100);
INSERT INTO ARTICLES VALUES (200);
INSERT INTO ARTICLES VALUES (300);

-------------------------------------------------------

SELECT D.article_id, D.GROU, D.OPT
FROM (SELECT C.*
     , @o:=CASE WHEN @h=ARTICLE_ID THEN @o ELSE cumul END max
     , @h:=ARTICLE_ID AS a_id
     FROM (SELECT article_id,
             B.GROU, B.OPT,             
             @r:= CASE WHEN @g = B.ARTICLE_ID THEN @r+1 ELSE 1 END cumul,                        
             @g:= B.ARTICLE_ID g                
             FROM CRITERIA B
             CROSS JOIN (SELECT @g:=0, @r:=0) T1
             WHERE (B.GROU='gender' AND B.OPT IN ('1'))
                    OR  (B.GROU='color'  AND B.OPT IN ('40', '30'))
                    OR  (B.GROU='size'   AND B.OPT BETWEEN 35.500000 AND 36.500000)
             ORDER BY article_id
    ) C
CROSS JOIN (SELECT @o:=0, @h:=0) T2
ORDER BY ARTICLE_ID, CUMUL DESC) D
WHERE max=3
;
输出:

article_id  GROU    OPT
100 gender  1
100 color   40
100 size    36

我知道你创建的索引解决了你的问题, 但是,为了使用伪备选方案(避免多个内部联接),您可以尝试这样的方法吗?(我只使用了三个条件进行了测试。您的条件应该插入到内部查询中。要仅选择满足所有条件的记录,您必须更改last WHERE条件(其中max=3,使用您上面编写的条件数;因此,如果您使用的是5个条件,则应在max=5的位置编写)。(为了便于使用,我更改了列组和选项的名称)。 这只是一个想法,所以请做一些测试,检查性能,请让我知道

CREATE TABLE CRITERIA (ARTICLE_ID INT, GROU VARCHAR(10), OPT VARCHAR(20), OPTION_VAL NUMERIC(12,2));
CREATE TABLE ARTICLES (ID INT);
INSERT INTO CRITERIA VALUES (100,'size','35',35);
INSERT INTO CRITERIA VALUES (100,'size','36',36);
INSERT INTO CRITERIA VALUES (100,'color','40',40);
INSERT INTO CRITERIA VALUES (100,'gender','1',1);
INSERT INTO CRITERIA VALUES (200,'size','36.2',36.2);
INSERT INTO CRITERIA VALUES (300,'size','36.2',36.2);
INSERT INTO ARTICLES VALUES (100);
INSERT INTO ARTICLES VALUES (200);
INSERT INTO ARTICLES VALUES (300);

-------------------------------------------------------

SELECT D.article_id, D.GROU, D.OPT
FROM (SELECT C.*
     , @o:=CASE WHEN @h=ARTICLE_ID THEN @o ELSE cumul END max
     , @h:=ARTICLE_ID AS a_id
     FROM (SELECT article_id,
             B.GROU, B.OPT,             
             @r:= CASE WHEN @g = B.ARTICLE_ID THEN @r+1 ELSE 1 END cumul,                        
             @g:= B.ARTICLE_ID g                
             FROM CRITERIA B
             CROSS JOIN (SELECT @g:=0, @r:=0) T1
             WHERE (B.GROU='gender' AND B.OPT IN ('1'))
                    OR  (B.GROU='color'  AND B.OPT IN ('40', '30'))
                    OR  (B.GROU='size'   AND B.OPT BETWEEN 35.500000 AND 36.500000)
             ORDER BY article_id
    ) C
CROSS JOIN (SELECT @o:=0, @h:=0) T2
ORDER BY ARTICLE_ID, CUMUL DESC) D
WHERE max=3
;
输出:

article_id  GROU    OPT
100 gender  1
100 color   40
100 size    36

键/值表确实很麻烦。但是,为了找到某些条件匹配项,请聚合数据:

select 
  a.*,
  ac.group AS "key",
  ac.option AS "value"
from articles a
join article_criterias ac on ac.article_id = a.article_id
where a.article_id in
(
  select article_id
  from article_criterias
  group by article_id
  having sum("group" = 'gender' and option = '1') > 0
     and sum("group" = 'color' and option in ('30','80')) > 0
     and sum("group" = 'size' and option_val between 35.5 and 36.5) > 0
     and sum("group" = 'size' and option_val between 36.5 and 37.5) > 0
     and sum("group" = 'size' and option_val between 37.5 and 38.5) > 0
     and sum("group" = 'size' and option_val between 38.5 and 39.5) > 0
     and sum("group" = 'size' and option_val between 41.5 and 42.5) > 0
     and sum("group" = 'size' and option_val between 45.5 and 46.5) > 0
)
order by a.article_id, ac.group, ac.option;
这将为您提供适用于性别1、颜色30和/或80的所有文章,以及所有列出的尺寸范围,以及它们的所有选项。(不过尺寸范围有点奇怪;例如,36.5的尺寸将满足两个范围。)你得到了这样的想法:按文章id分组并使用
HAVING
,以便只获得符合标准的文章id

至于你想要的索引

create index idx on article_criterias(article_id, "group", option, option_val);

键/值表确实很麻烦。但是,为了找到某些条件匹配项,请聚合数据:

select 
  a.*,
  ac.group AS "key",
  ac.option AS "value"
from articles a
join article_criterias ac on ac.article_id = a.article_id
where a.article_id in
(
  select article_id
  from article_criterias
  group by article_id
  having sum("group" = 'gender' and option = '1') > 0
     and sum("group" = 'color' and option in ('30','80')) > 0
     and sum("group" = 'size' and option_val between 35.5 and 36.5) > 0
     and sum("group" = 'size' and option_val between 36.5 and 37.5) > 0
     and sum("group" = 'size' and option_val between 37.5 and 38.5) > 0
     and sum("group" = 'size' and option_val between 38.5 and 39.5) > 0
     and sum("group" = 'size' and option_val between 41.5 and 42.5) > 0
     and sum("group" = 'size' and option_val between 45.5 and 46.5) > 0
)
order by a.article_id, ac.group, ac.option;
这将为您提供适用于性别1、颜色30和/或80的所有文章,以及所有列出的尺寸范围,以及它们的所有选项。(不过尺寸范围有点奇怪;例如,36.5的尺寸将满足两个范围。)你得到了这样的想法:按文章id分组并使用
HAVING
,以便只获得符合标准的文章id

至于你想要的索引

create index idx on article_criterias(article_id, "group", option, option_val);

检查索引,查找内部联接条件中使用的字段和where条件。索引将进行快速搜索。您在中遇到的问题称为“实体属性值”或“EAV”.就关系数据库而言,您已经从地狱中得到了反模式。您需要不同的数据库或不同的方法。虽然我同意spaceman的观点,但您确定查询真的能如预期的那样工作吗?您的查询当前将找到特定性别的鞋子,颜色为80或(!)30,尺码为36和(!)37(以及其他),因此,如果在除39以外的所有尺寸中都有,则不会产生任何结果。如果对尺寸也使用
,则可以在此处节省大量连接/时间,类似于颜色:仅连接一次,例如,使用
连接…criteria_size2.group=“size”和(criteria_size2.option_val介于35.5和36.5之间或criteria_size2.option_val介于36.5和37.5之间或…
@LoztInSpace是的,我知道…但是,现在更改数据库结构不是一个选项。您应该已经看到了原始查询的工作方式(在最低分支中有3个嵌套循环,其中SQL)…请注意,group、option和key都是保留字,因此它们都不是表/列标识符的理想选择。请检查索引、内部联接条件中使用的字段以及where条件。索引将进行快速搜索。您在中遇到的问题称为“实体属性值”或“EAV”.就关系数据库而言,您已经从地狱中得到了反模式。您需要不同的数据库或不同的方法。虽然我同意spaceman的观点,但您确定查询真的能如预期的那样工作吗?您的查询当前将找到特定性别的鞋子,颜色为80或(!)30,尺码为36和(!)37(以及其他),因此,如果在除39以外的所有尺寸中都有,则不会产生任何结果。如果对尺寸也使用
,则可以在此处节省大量连接/时间,类似于颜色:仅连接一次,例如,使用
连接…criteria_size2.group=“size”和(criteria_size2.option_val介于35.5和36.5之间或criteria_size2.option_val介于36.5和37.5之间或…
@LoztInSpace是的,我知道…但是,现在更改数据库结构不是一个选项。您应该已经看到了原始查询的工作方式(在最低分支中有3个嵌套循环,其中SQL)…请注意,group、option和key都是保留字,因此它们都较少