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

sbo*_*oss 8 mysql parameters performance stored-procedures 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
Run Code Online (Sandbox Code Playgroud)

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

WHERE
  domain.country = 1
Run Code Online (Sandbox Code Playgroud)

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"
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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

sea*_*awk 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.
Run Code Online (Sandbox Code Playgroud)

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

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

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

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


Ric*_*mes 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
Run Code Online (Sandbox Code Playgroud)

根据EXPLAIN它,它必须扫描link每次~8703行.

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

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;
Run Code Online (Sandbox Code Playgroud)

然后

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; 
Run Code Online (Sandbox Code Playgroud)

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

优化2

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

IFNULL(GROUP_CONCAT(category.name SEPARATOR ', '), '-') AS categories,
LEFT JOIN category ON ...
Run Code Online (Sandbox Code Playgroud)

- >

IFNULL(
    ( SELECT GROUP_CONCAT(category.name SEPARATOR ', ')
          FROM category
          WHERE category.id = domain_category.category
    ),
'-') AS categories,
Run Code Online (Sandbox Code Playgroud)

如果您使用变体和我的变体执行此操作,可以观察到它可能的原因:

SELECT COUNT(*) FROM ( the select, but without the GROUP BY or ORDER BY );
Run Code Online (Sandbox Code Playgroud)

你的变体(假设很多类别等)会有更大的变化COUNT.这意味着您的查询正在构建一个更大的tmp表来提供GROUP BYORDER BY.因此慢.

优化3

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


Mih*_*hai 4

如果你一路动态走下去,我用测试不覆盖你原来的过程,还要确保你没有一个testing过程已经在那里

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 ;
Run Code Online (Sandbox Code Playgroud)

然后使用

CALL `testing`(1);
Run Code Online (Sandbox Code Playgroud)