2015-07-21 137 views
0

我有一个使用EAV model生成属性的CRM系统。由于您可能非常清楚EAV模型需要复杂的查询来提取数据,因此这个问题非常明显。每个属性都必须在单独的列中返回。如何提高MySQL中的子查询性能

当使用子查询时,MySQL的性能很糟糕。我必须通过使用give where子句,排序顺序和限制“如果有的”来分析它们来找到更好的方式来编写我的查询!

通过子查询我裁判的查询看起来像这样

SELECT a.account_name, a.account_type, a.status, a.account_id, s.fieldValue, s2.last_training_on, s3.fieldValue 
FROM accounts AS a 
INNER JOIN clients AS c ON c.client_id = a.client_id 
LEFT JOIN (
    SELECT p.related_to AS account_id, decimal_value AS fieldValue 
    FROM df_answers_text AS p 
    INNER JOIN df_field_to_client_relation AS r ON r.field_id = p.field_id 
    WHERE p.field_id = '19' AND r.client_id = '7'; 
) AS s ON s.account_id = a.account_id 
LEFT JOIN (
    SELECT p.related_to AS account_id, datetime_value AS last_training_on 
    FROM df_answers_text AS p 
    INNER JOIN df_field_to_client_relation AS r ON r.field_id = p.field_id 
    WHERE p.field_id = '10' AND r.client_id = '7'; 
) AS s2 ON s2.account_id = a.account_id 
LEFT JOIN (
    SELECT 
     p.related_to 
    , CAST(GROUP_CONCAT(o.label SEPARATOR " | ") AS CHAR(255)) AS fieldValue 
    FROM df_answer_predefined AS p 
    INNER JOIN df_fields_options AS o ON o.option_id = p.option_id 
    INNER JOIN df_field_to_client_relation AS r ON r.field_id = o.field_id 
    WHERE o.is_place_holder = 0 AND o.field_id = '16' AND r.field_id = '16' AND r.client_id = '7' 
    GROUP BY p.related_to; 
) AS s3 ON s3.related_to = a.account_id 
WHERE c.client_id = '7' AND c.status = 'Active' AND (a.account_type = 'TEST' OR a.account_type = 'VALUE' OR s2.last_training_on > '2015-01-01 00:00:00') AND (s.fieldValue = 'Medium' OR s.fieldValue = 'Low' OR a.expType = 'Very High') 
ORDER BY a.account_name 
LIMIT 500; 

我想和子查询这样

CREATE TEMPORARY TABLE s (KEY(account_id, fieldValue)) ENGINE = MEMORY 
SELECT p.related_to AS account_id, decimal_value AS fieldValue 
FROM df_answers_text AS p 
INNER JOIN df_field_to_client_relation AS r ON r.field_id = p.field_id 
WHERE p.field_id = '19' AND r.client_id = '7'; 

CREATE TEMPORARY TABLE s2 (KEY(account_id, INDEX USING BTREE last_training_on)) ENGINE = MEMORY 
SELECT p.related_to AS account_id, datetime_value AS last_training_on 
FROM df_answers_text AS p 
INNER JOIN df_field_to_client_relation AS r ON r.field_id = p.field_id 
WHERE p.field_id = '10' AND r.client_id = '7'; 


    CREATE TEMPORARY TABLE s3 (KEY(related_to, fieldValue)) ENGINE = MEMORY 
    SELECT 
     p.related_to 
    , CAST(GROUP_CONCAT(o.label SEPARATOR " | ") AS CHAR(255)) AS fieldValue 
    FROM df_answer_predefined AS p 
    INNER JOIN df_fields_options AS o ON o.option_id = p.option_id 
    INNER JOIN df_field_to_client_relation AS r ON r.field_id = o.field_id 
    WHERE o.is_place_holder = 0 AND o.field_id = '16' AND r.field_id = '16' AND r.client_id = '7' 
    GROUP BY p.related_to; 


    CREATE TEMPORARY TABLE s3 (KEY(related_to)) ENGINE = MEMORY 
    SELECT 
     p.related_to 
    , CAST(GROUP_CONCAT(o.label SEPARATOR " | ") AS CHAR(255)) AS fieldValue 
    FROM df_answer_predefined AS p 
    INNER JOIN df_fields_options AS o ON o.option_id = p.option_id 
    INNER JOIN df_field_to_client_relation AS r ON r.field_id = o.field_id 
    WHERE o.is_place_holder = 0 AND o.field_id = '16' AND r.field_id = '16' AND r.client_id = '7' 
    GROUP BY p.related_to; 


Then my new query will look like this 

    SELECT a.account_name, a.account_type, a.status, a.account_id, s.fieldValue, s2.last_training_on, s3.fieldValue 
    FROM accounts AS a 
    INNER JOIN clients AS c ON c.client_id = a.client_id 
    LEFT JOIN s ON s.account_id = a.account_id 
    LEFT JOIN s2 ON s2.account_id = a.account_id 
    LEFT JOIN s3 ON s2.related_to = a.account_id 
    WHERE c.client_id = '7' AND c.status = 'Active' AND (a.account_type = 'TEST' OR a.account_type = 'VALUE' OR s2.last_training_on > '2015-01-01 00:00:00') AND (s.fieldValue = 'Medium' OR s.fieldValue = 'Low' OR a.expType = 'Very High') 
    ORDER BY a.account_name 
    LIMIT 500; 

    DROP TEMPORARY TABLE s, s2; 

内容创建使用MEMORY引擎临时表我现在面临的问题是,临时表将创建一个数据库中可用的全部数据的临时表,这会占用大量时间。但我的外部查询只查找按a.account_name排序的500条记录。如果临时表有100万条记录会浪费时间,显然会给我带来不好的表现。

我希望找到一个更好的方法来对子句传递给子查询,以这种方式,我只会创建一个临时表所需要的数据的外部查询

注:这些查询使用GUI生成动态。我无法弄清楚如何提取逻辑/子句并将它们正确地传递给子查询。

质询

  • 我如何看where子句,解析它们,并将它们传递给子查询拒绝在子奎雷斯的数据量?如果这个句子叫“AND”,那么我的生活会更容易,但因为我有混合或“AND”和“OR”,这非常复杂。
  • 有没有更好的方法来解决这个问题,而不是使用临时表。

EDITED 这里是我的表定义

CREATE TABLE df_answer_predefined ( answer_id int(11) unsigned NOT NULL AUTO_INCREMENT, field_id int(11) unsigned DEFAULT NULL, related_to int(11) unsigned DEFAULT NULL, option_id int(11) unsigned DEFAULT NULL, created_by int(11) unsigned NOT NULL, created_on datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (answer_id), UNIQUE KEY un_row (field_id,option_id,related_to), KEY field_id (field_id), KEY related_to (related_to), KEY to_delete (field_id,related_to), KEY outter_view (field_id,option_id,related_to) ) ENGINE=InnoDB AUTO_INCREMENT=4946214 DEFAULT CHARSET=utf8;

`CREATE TABLE df_fields_options (
    option_id int(11) unsigned NOT NULL AUTO_INCREMENT, 
    field_id int(11) unsigned NOT NULL, 
    label varchar(255) DEFAULT NULL, 
    is_place_holder tinyint(1) NOT NULL DEFAULT '0', 
    is_default tinyint(1) NOT NULL DEFAULT '0', 
    sort smallint(3) NOT NULL DEFAULT '1', 
    status tinyint(1) NOT NULL DEFAULT '1', 
    PRIMARY KEY (option_id), 
    KEY i (field_id), 
    KEY d (option_id,field_id,is_place_holder) 
) ENGINE=InnoDB AUTO_INCREMENT=155 DEFAULT CHARSET=utf8;` 


`CREATE TABLE df_field_to_client_relation (
    relation_id int(11) unsigned NOT NULL AUTO_INCREMENT, 
    client_id int(11) unsigned DEFAULT NULL, 
    field_id int(11) unsigned DEFAULT NULL, 
    PRIMARY KEY (relation_id), 
    UNIQUE KEY unique_row (field_id,client_id), 
    KEY client_id (client_id), 
    KEY flient_id (field_id) 
) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8;` 


`CREATE TABLE df_answers_text (
    answer_id int(11) unsigned NOT NULL AUTO_INCREMENT, 
    notes varchar(20000) DEFAULT NULL, 
    datetime_value datetime DEFAULT NULL, 
    date_value date DEFAULT NULL, 
    us_phone_number char(10) DEFAULT NULL, 
    field_id int(11) unsigned DEFAULT NULL, 
    related_to int(11) unsigned DEFAULT NULL, 
    created_by int(11) unsigned NOT NULL, 
    created_on datetime DEFAULT CURRENT_TIMESTAMP, 
    modified_by int(11) DEFAULT NULL, 
    modified_on datetime DEFAULT NULL, 
    big_unsigned_value bigint(20) DEFAULT NULL, 
    big_signed_value bigint(19) DEFAULT NULL, 
    unsigned_value int(11) DEFAULT NULL, 
    signed_value int(10) DEFAULT NULL, 
    decimal_value decimal(18,4) DEFAULT NULL, 
    PRIMARY KEY (answer_id), 
    UNIQUE KEY unique_answer (field_id,related_to), 
    KEY field_id (field_id), 
    KEY related_to (related_to), 
    KEY big_unsigned_value (big_unsigned_value), 
    KEY big_signed_value (big_signed_value), 
    KEY unsigned_value (unsigned_value), 
    KEY signed_value (signed_value), 
    KEY decimal_Value (decimal_value) 
) ENGINE=InnoDB AUTO_INCREMENT=2458748 DEFAULT CHARSET=utf8;` 

这需要时间最多的查询是第三次查询与别名s3

这里是我们花费很长时间“2秒”的查询执行计划

enter image description here

+1

我没有提供任何实质性的帮助,但我的初步印象是,你可能从没有在你的两个子查询潜在的局部交叉联接看到一些好处。 – Uueerdo

+0

我有更多的问题比答案。哪些索引被定义?表格的相对大小是多少?你怎么知道子查询一直在使用?你有解释计划,我们可以看看吗? – schtever

+0

我建议你提供正确的创建和插入语句和期望的结果。 – Strawberry

回答

0
UNIQUE(a,b,c) 
INDEX (a) 

删除索引,因为唯一密钥是一个INDEX 的INDEX是唯一的前缀。

PRIMARY KEY(d) 
UNIQUE(a,b,c) 

为什么d呢?简单地说,PRIMARY KEY(a,b,c)

FROM (SELECT ...) 
JOIN (SELECT ...) ON ... 

优化不佳(直到5.6.6)。只要有可能,将JOIN (SELECT)转换为与表格连接。如你所说,使用TMP表可以会更好,如果可以添加合适的索引到TMP表。最好是尽量避免超过一个“表”是一个子查询。

在许多一对多的关系表,不包括在表中的ID,而不是有只有

PRIMARY KEY (a,b), -- for enforcing uniqueness, providing a PK, and going one direction 
INDEX  (b,a) -- for going the other way. 

的EXPLAIN似乎并不符合您所提供的选择。没有其他的,每个都是无用的。

另一种方法是威力帮助...而不是

SELECT ..., s2.foo, ... 
    ... 
    JOIN (SELECT ... FROM x WHERE ...) AS s2 ON s2.account_id = a.account_id 

看看你是否可以重新制定它:

SELECT ..., 
     (SELECT foo FROM x WHERE ... AND related = a.account_id) AS foo, ... 
    ... 

也就是说,相关子查询用于替代JOIN子查询你需要的一个价值。

底线是,EAV模型吸。

嗯...我不认为这有必要在所有的,因为r没有在他查询其他地方使用...

INNER JOIN df_field_to_client_relation AS r ON r.field_id = p.field_id 
    WHERE p.field_id = '19' AND r.client_id = '7' 

这似乎是等同于

WHERE EXISTS (SELECT * FROM df_field_to_client_relation 
       WHERE field_id = '19' AND client_id = '7') 

但为什么还要检查是否存在?