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

Using a parameter in a MySQL stored procedure WHERE-CLAUSE slows down performance

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

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"

显示创建 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 秒。有什么想法吗?

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

但一般情况下会发生

  1. Where 子句对整数值运行速度更快
  2. 如果您非常频繁地对任何列执行 where 子句而不是索引该列。索引有助于加快检索速度。但与此同时,您需要在索引列时做出明智的决定。

如果您正在寻找其他信息,请告诉我。

如果你一直保持动态,我使用测试不会覆盖你原来的过程,还要确保你没有 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 ;

然后使用

CALL `testing`(1);

优化 1

减速的部分原因是重复执行 "dependent subquery":

      ( 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,它每次必须扫描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;

然后

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 table 以提供给 GROUP BYORDER BY。因此较慢。

优化 3

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

根据问题,@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 engine caches query and result. The query cache stores the text of a SELECT statement together with the corresponding result that was sent to the client. If an identical statement is received later, the server retrieves the results from the query cache rather than parsing and executing the statement again.

因此,在您的情况下,很可能在 mysql 引擎实际解析和执行查询时发现 execution time of 5.070s,而在从中检索结果集时发现 execution time of 0.06s查询缓存。

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

首先澄清你的疑问,为什么如果通过参数传递值,查询会花费时间。正如 Rick 和 Jaydatt 在他们的帖子中提到的,这可能是由于查询缓存造成的,因此您可以首先通过添加 SQL_NO_CACHE 关键字(例如 SELECT SQL_NO_CACHE domain.id, IFNULL(domain.indexed, '-') AS indexed,....

来执行查询来消除您的疑虑

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

为了进一步优化此查询,甚至 Rick 也为您提供了如此多的有用选项,您还可以查看以下选项以及这些选项。

首先检查 link.from_domain 字段是否被索引,因为解释显示所有行都从 link table.

扫描

您还可以使用以下方法检查查询性能-

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;