在MySQL存储过程中使用参数WHERE-CLAUSE会降低性能

时间:2015-08-18 08:40:46

标签: mysql performance stored-procedures parameters left-join

我有一个声明如下的存储过程:

CREATE DEFINER=`blabla`@`%` PROCEDURE `getAllDomainsByCountry`(IN dom_id INT)

BEGIN
SELECT 
domain.id,
IFNULL(domain.indexed, '-') AS indexed,
domain.name,
country.language_code,
IFNULL(ip_adress.adress, '-') AS adress,
IFNULL(GROUP_CONCAT(category.name
            SEPARATOR ', '),
        '-') AS categories,
IFNULL(GROUP_CONCAT(category.id
            SEPARATOR ', '),
        '-') AS categories_id,
(SELECT 
        IFNULL(GROUP_CONCAT(DISTINCT client.name
                        SEPARATOR ', '),
                    '-')
    FROM
        link
            LEFT JOIN
        client_site ON link.client_site = client_site.id
            LEFT JOIN
        client ON client.id = client_site.client
    WHERE
        link.from_domain = domain.id) AS clients,
IFNULL(domain_host.name, '-') AS domain_host_account,
IFNULL(content_host.name, '-') AS content_host,
status.id AS status,
status.name AS status_name
FROM
domain
    LEFT JOIN
ip_adress ON domain.ip = ip_adress.id
    LEFT JOIN
domain_category ON domain.id = domain_category.domain
    LEFT JOIN
category ON domain_category.category = category.id
    LEFT JOIN
country ON domain.country = country.id
    LEFT JOIN
domain_host_account ON domain.domain_host_account = domain_host_account.id
    LEFT JOIN
domain_host ON domain_host_account.host = domain_host.id
    LEFT JOIN
content_host ON domain.content_host = content_host.id
    LEFT JOIN
domain_status ON domain.id = domain_status.domain
    LEFT JOIN
status ON domain_status.status = status.id
WHERE
domain.country = dom_id
GROUP BY domain.id
ORDER BY domain.name;  
END

如果我将参数dom_id的用法替换为静态整数,即:

WHERE
  domain.country = 1

MySQL版本:5.5.41

说明:

id,select_type,table,type,possible_keys,key,key_len,ref,rows,Extra
1,PRIMARY,domain,ref,idx_domain_country,idx_domain_country,5,const,1858,"Using where; Using temporary; Using filesort"
1,PRIMARY,ip_adress,eq_ref,PRIMARY,PRIMARY,4,dominfo.domain.ip,1,
1,PRIMARY,domain_category,ref,FK_domain_category_domain_idx,FK_domain_category_domain_idx,5,dominfo.domain.id,1,
1,PRIMARY,category,eq_ref,PRIMARY,PRIMARY,4,dominfo.domain_category.category,1,
1,PRIMARY,country,const,PRIMARY,PRIMARY,4,const,1,
1,PRIMARY,domain_host_account,eq_ref,PRIMARY,PRIMARY,4,dominfo.domain.domain_host_account,1,
1,PRIMARY,domain_host,eq_ref,PRIMARY,PRIMARY,4,dominfo.domain_host_account.host,1,
1,PRIMARY,content_host,eq_ref,PRIMARY,PRIMARY,4,dominfo.domain.content_host,1,
1,PRIMARY,domain_status,ALL,NULL,NULL,NULL,NULL,1544,
1,PRIMARY,status,eq_ref,PRIMARY,PRIMARY,4,dominfo.domain_status.status,1,
2,"DEPENDENT SUBQUERY",link,ALL,NULL,NULL,NULL,NULL,8703,"Using where"
2,"DEPENDENT SUBQUERY",client_site,eq_ref,PRIMARY,PRIMARY,4,dominfo.link.client_site,1,
2,"DEPENDENT SUBQUERY",client,eq_ref,PRIMARY,PRIMARY,4,dominfo.client_site.client,1,"Using where"

SHOW CREATE TABLE域名:

CREATE TABLE `domain` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(67) DEFAULT NULL,
`domain_host_account` int(11) DEFAULT NULL,
`content_host` int(11) DEFAULT NULL,
`ip` varchar(45) DEFAULT NULL,
`historic_content` tinytext,
`redirected` int(11) DEFAULT NULL,
`ftp_account` tinyint(1) DEFAULT ''0'',
`comment` tinytext,
`country` int(11) DEFAULT NULL,
`redirected_text` varchar(45) DEFAULT NULL,
`status_text` varchar(500) DEFAULT NULL,
`dhost_text` varchar(500) DEFAULT NULL,
`chost_text` varchar(500) DEFAULT NULL,
`category_text` varchar(150) DEFAULT NULL,
`dhost_acc_text` varchar(45) DEFAULT NULL,
`indexed` tinyint(1) DEFAULT NULL,
`indexed_checked` date DEFAULT NULL,
`origin` tinyint(1) DEFAULT ''0'',
PRIMARY KEY (`id`),
KEY `FK_domain_host_account_idx` (`domain_host_account`),
KEY `idx_domain_ip` (`ip`),
KEY `idx_domain_country` (`country`),
KEY `idx_domain_domain_host_account` (`domain_host_account`),
KEY `idx_domain_content_host` (`content_host`)
) ENGINE=InnoDB AUTO_INCREMENT=12598 DEFAULT CHARSET=latin1

该过程将执行0.06秒,而使用参数" dom_id",传递整数值1,将导致执行时间为5.070秒。有什么想法吗?

5 个答案:

答案 0 :(得分:8)

根据这个问题,@ sboss想知道:

的行为
The procedure will take 0.06s to execute with static int whereas using the parameter 
"dom_id", passing integer value of 1, it will result in an execution 
time of 5.070s.

如果我们看到mysql引擎如何工作,这个行为很容易理解。

  

MySQL引擎缓存查询和结果。查询缓存存储文本   一个SELECT语句以及相应的结果   发送给客户。如果稍后收到相同的陈述,则   服务器从查询缓存中检索结果而不是解析   并再次执行该声明。

因此,在您的情况下,最有可能在查询实际由mysql引擎解析和执行时找到execution time of 5.070s,并且在从查询缓存中检索结果集时找到execution time of 0.06s

请详细参阅文档:https://dev.mysql.com/doc/refman/5.5/en/query-cache.html

答案 1 :(得分:5)

优化1

部分减速是重复执行的“依赖子查询”:

      ( SELECT  IFNULL(GROUP_CONCAT(DISTINCT client.name SEPARATOR ', '), '-')
            FROM  link
            LEFT JOIN  client_site ON link.client_site = client_site.id
            LEFT JOIN  client ON client.id = client_site.client
            WHERE  link.from_domain = domain.id
      ) AS clients

根据EXPLAIN,它必须每次扫描所有〜{8703行link

我不认为它可以在同一个查询中简化。相反,我认为这将是有用的:

CREATE TEMPORARY TABLE t_clients (
    PRIMARY KEY(from_domain)
)
    SELECT  link.from_domain,
            IFNULL(GROUP_CONCAT(DISTINCT client.name SEPARATOR ', '), '-')
        FROM  link
        LEFT JOIN  client_site ON link.client_site = client_site.id
        LEFT JOIN  client ON client.id = client_site.client;

然后

SELECT  domain.id, IFNULL(domain.indexed, '-') AS indexed, domain.name,
        country.language_code, IFNULL(ip_adress.adress, '-') AS adress,
        IFNULL(GROUP_CONCAT(category.name SEPARATOR ', '), '-') AS categories,
        IFNULL(GROUP_CONCAT(category.id SEPARATOR ', '), '-') AS categories_id,
        t_clients.clients AS clients,                          -- Changed
        IFNULL(domain_host.name, '-') AS domain_host_account,
        IFNULL(content_host.name, '-') AS content_host,
        status.id AS status, status.name AS status_name
    FROM  domain
    LEFT JOIN t_clients  ON t_clients.from_domain = domain.id  -- Added
    LEFT JOIN  ip_adress ON domain.ip = ip_adress.id
    LEFT JOIN  domain_category ON domain.id = domain_category.domain
    LEFT JOIN  category ON domain_category.category = category.id
    LEFT JOIN  country ON domain.country = country.id
    LEFT JOIN  domain_host_account ON domain.domain_host_account = domain_host_account.id
    LEFT JOIN  domain_host ON domain_host_account.host = domain_host.id
    LEFT JOIN  content_host ON domain.content_host = content_host.id
    LEFT JOIN  domain_status ON domain.id = domain_status.domain
    LEFT JOIN  status ON domain_status.status = status.id
    WHERE  domain.country = dom_id
    GROUP BY  domain.id
    ORDER BY  domain.name; 

您可以尝试PREPARE方法是否更快。在我做过的一个(更简单的)测试中,它似乎并不重要。

优化2

另一个潜在的加速是在子查询中执行GROUP_CONCATs而不是收集大量行,然后进行折叠。请注意,您必须使用GROUP BY。这种技术可能能够消除这种情况。例如:

IFNULL(GROUP_CONCAT(category.name SEPARATOR ', '), '-') AS categories,
LEFT JOIN category ON ...

- >

IFNULL(
    ( SELECT GROUP_CONCAT(category.name SEPARATOR ', ')
          FROM category
          WHERE category.id = domain_category.category
    ),
'-') AS categories,

如果你对你的变体和我的变体这样做,可能的原因可以被观察到:

SELECT COUNT(*) FROM ( the select, but without the GROUP BY or ORDER BY );

您的变体将(假设许多类别等)将具有更大的COUNT。这意味着您的查询正在构建一个更大的tmp表,以便提供给GROUP BYORDER BY。因此速度较慢。

优化3

如果你设法摆脱所有聚合(GROUP_CONCAT),那么添加INDEX(country, name)应该通过删除两个FILESORTs来进一步优化它。

答案 2 :(得分:4)

如果你一直动态,我用测试不要覆盖原来的proc,同时确保你没有testing proc已经存在

DROP PROCEDURE IF EXISTS `testing`;
DELIMITER //
CREATE PROCEDURE `testing`(IN `test` INT)
 BEGIN
SET @id=test;
SET @query="SELECT 
domain.id,
IFNULL(domain.indexed, '-') AS indexed,
domain.name,
country.language_code,
IFNULL(ip_adress.adress, '-') AS adress,
IFNULL(GROUP_CONCAT(category.name
            SEPARATOR ', '),
        '-') AS categories,
IFNULL(GROUP_CONCAT(category.id
            SEPARATOR ', '),
        '-') AS categories_id,
(SELECT 
        IFNULL(GROUP_CONCAT(DISTINCT client.name
                        SEPARATOR ', '),
                    '-')
    FROM
        link
            LEFT JOIN
        client_site ON link.client_site = client_site.id
            LEFT JOIN
        client ON client.id = client_site.client
    WHERE
        link.from_domain = domain.id) AS clients,
IFNULL(domain_host.name, '-') AS domain_host_account,
IFNULL(content_host.name, '-') AS content_host,
status.id AS status,
status.name AS status_name
FROM
domain
    LEFT JOIN
ip_adress ON domain.ip = ip_adress.id
    LEFT JOIN
domain_category ON domain.id = domain_category.domain
    LEFT JOIN
category ON domain_category.category = category.id
    LEFT JOIN
country ON domain.country = country.id
    LEFT JOIN
domain_host_account ON domain.domain_host_account = domain_host_account.id
    LEFT JOIN
domain_host ON domain_host_account.host = domain_host.id
    LEFT JOIN
content_host ON domain.content_host = content_host.id
    LEFT JOIN
domain_status ON domain.id = domain_status.domain
    LEFT JOIN
status ON domain_status.status = status.id
WHERE
domain.country = ?
GROUP BY domain.id
ORDER BY domain.name;"  

PREPARE sqlquery FROM @query;
EXECUTE sqlquery USING @id;
END;
//
DELIMITER ;

然后使用

CALL `testing`(1);

答案 3 :(得分:0)

您在这里说,您可以在使用WHERE时快速获取数据   domain.country = 1 代替 哪里   domain.country = dom_id 但我无法在您的查询中看到domain.country上的where子句。你能澄清一下吗。

但总的来说发生了什么

  1. Where子句在整数值上运行得更快
  2. 如果您经常在任何列上执行where子句,则可以索引该列。索引有助于加快检索速度。但与此同时,您需要在索引列时做出明智的决定。
  3. 如果您正在寻找其他任何内容,请与我们联系。

答案 4 :(得分:0)

首先清楚你的疑问,如果通过参数传递值,查询会花费时间。正如Rick和Jaydatt在帖子中提到的,可能是由于查询缓存,所以你可以先用额外的SQL_NO_CACHE关键字SELECT SQL_NO_CACHE domain.id, IFNULL(domain.indexed, '-') AS indexed,....

执行查询来清除这个疑问。

我想现在你应该得到约。同时由两个查询。

进一步优化此查询,即使Rick为您提供了许多有用的选项,您也可以选择以下选项。

首先检查link.from_domain字段是否已编入索引,因为解释显示所有行都是从链接表中扫描。

您还可以使用以下方法检查查询效果 -

SELECT 
domain.id,
IFNULL(domain.indexed, '-') AS indexed,
domain.name,
country.language_code,
IFNULL(ip_adress.adress, '-') AS adress,
IFNULL(GROUP_CONCAT(category.name
            SEPARATOR ', '),
        '-') AS categories,
IFNULL(GROUP_CONCAT(category.id
            SEPARATOR ', '),
        '-') AS categories_id,
(SELECT 
        IFNULL(GROUP_CONCAT(DISTINCT client.name
                        SEPARATOR ', '),
                    '-')
    FROM
        link
            LEFT JOIN
        client_site ON link.client_site = client_site.id
            LEFT JOIN
        CLIENT ON client.id = client_site.client
    WHERE
        link.from_domain = domain.id) AS clients,
IFNULL(domain_host.name, '-') AS domain_host_account,
IFNULL(content_host.name, '-') AS content_host,
status.id AS STATUS,
status.name AS status_name
FROM
(
SELECT id,indexed,`name`,ip,country,domain_host_account,content_host 
FROM domain 
WHERE country = dom_id
) AS domain
    LEFT JOIN
ip_adress ON domain.ip = ip_adress.id
    LEFT JOIN
domain_category ON domain.id = domain_category.domain
    LEFT JOIN
category ON domain_category.category = category.id
    LEFT JOIN
country ON domain.country = country.id
    LEFT JOIN
domain_host_account ON domain.domain_host_account = domain_host_account.id
    LEFT JOIN
domain_host ON domain_host_account.host = domain_host.id
    LEFT JOIN
content_host ON domain.content_host = content_host.id
    LEFT JOIN
domain_status ON domain.id = domain_status.domain
    LEFT JOIN
STATUS ON domain_status.status = status.id
GROUP BY domain.id
ORDER BY domain.name;