如何找到针对不同数据库的查询执行时间差异的原因?

Posted

技术标签:

【中文标题】如何找到针对不同数据库的查询执行时间差异的原因?【英文标题】:How to find the reason for the difference in the execution time of a query against different databases? 【发布时间】:2018-06-07 13:27:19 【问题描述】:

我有两个具有相同架构的数据库。一个数据库来自生产,另一个是测试数据库。我正在对数据库中的单个表进行查询。在生产表上查询大约需要 4.3 秒,而在测试数据库上大约需要 130 毫秒。 .但是,生产表的记录少于 50.000 条,而我在测试表中植入了超过 100.000 条记录。我比较了这两个表,它们都有相同的索引。对我来说,问题似乎出在数据上。在播种时,我尝试生成尽可能随机的数据,以便模拟生产条件,但仍然无法重现慢速查询。

我查看了来自EXPLAIN 的两个查询的结果。它们在最后两列有显着差异。

生产:

+-------+-------------------------+
| rows  | Extra                   |
+-------+-------------------------+
| 24459 | Using where             |
| 46    | Using where; Not exists |
+-------+-------------------------+

测试:

+------+------------------------------------+
| rows | Extra                              |
+------+------------------------------------+
| 3158 | Using index condition; Using where |
| 20   | Using where; Not exists            |
+------+------------------------------------+

生产表的创建语句为:

CREATE TABLE `usage_logs` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL,
  `operation` varchar(30) COLLATE utf8_unicode_ci NOT NULL,
  `check_time` datetime NOT NULL,
  `check_in_log_id` int(11) DEFAULT NULL,
  `daily_usage_id` int(11) DEFAULT NULL,
  `duration_units` decimal(11,2) DEFAULT NULL,
  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
  `created_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  `facility_id` int(11) NOT NULL,
  `notes` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `mac_address` varchar(20) COLLATE utf8_unicode_ci NOT NULL DEFAULT '00:00:00:00:00:00',
  `login` varchar(40) COLLATE utf8_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `index_usage_logs_on_user_id` (`user_id`),
  KEY `index_usage_logs_on_check_in_log_id` (`check_in_log_id`),
  KEY `index_usage_logs_on_facility_id` (`facility_id`),
  KEY `index_usage_logs_on_check_time` (`check_time`),
  KEY `index_usage_logs_on_mac_address` (`mac_address`),
  KEY `index_usage_logs_on_operation` (`operation`)
) ENGINE=InnoDB AUTO_INCREMENT=145147 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

而在测试数据库中同样是:

CREATE TABLE `usage_logs` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL,
  `operation` varchar(30) COLLATE utf8_unicode_ci NOT NULL,
  `check_time` datetime NOT NULL,
  `check_in_log_id` int(11) DEFAULT NULL,
  `daily_usage_id` int(11) DEFAULT NULL,
  `duration_units` decimal(11,2) DEFAULT NULL,
  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
  `created_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  `facility_id` int(11) NOT NULL,
  `notes` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `mac_address` varchar(20) COLLATE utf8_unicode_ci NOT NULL DEFAULT '00:00:00:00:00:00',
  `login` varchar(40) COLLATE utf8_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `index_usage_logs_on_check_in_log_id` (`check_in_log_id`),
  KEY `index_usage_logs_on_check_time` (`check_time`),
  KEY `index_usage_logs_on_facility_id` (`facility_id`),
  KEY `index_usage_logs_on_mac_address` (`mac_address`),
  KEY `index_usage_logs_on_operation` (`operation`),
  KEY `index_usage_logs_on_user_id` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=104001 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

完整的查询是:

SELECT `usage_logs`.*
FROM `usage_logs`
LEFT OUTER JOIN usage_logs AS usage_logs_latest ON usage_logs.facility_id = usage_logs_latest.facility_id
AND usage_logs.user_id = usage_logs_latest.user_id
AND usage_logs.mac_address = usage_logs_latest.mac_address
AND usage_logs.check_time < usage_logs_latest.check_time
WHERE `usage_logs`.`facility_id` = 5
  AND `usage_logs`.`operation` = 'checkIn'
  AND (usage_logs.check_time >= '2018-06-08 00:00:00')
  AND (usage_logs.check_time <= '2018-06-08 11:23:05')
  AND (usage_logs_latest.id IS NULL)

我在同一台机器上针对两个不同的数据库执行查询,所以我不认为其他进程会干扰结果。

这个结果是什么意思,我可以采取哪些进一步的步骤来找出执行时间差异很大的原因?

【问题讨论】:

您的生产系统上没有定义索引?你能检查一下吗? 在测试表中您正在使用索引条件,而在生产中您没有索引条件。你应该创建它 @nacho 你如何创建索引条件? 试试这个 SET optimizer_switch = 'index_condition_pushdown=on';在生产系统中 生产中的 50K 记录和 AUTO_INCREMENT=145147 - 您的生产表是否会碎片化以影响性能?生产服务器上的任何其他(非mysql)活动?两台服务器上的内存和存储(本地磁盘/SAN)是否相同? 【参考方案1】:

您使用的是什么 MySQL 版本?

有许多因素导致优化器做出决定

从哪个表开始; (我们看不出它们是否不同) 使用哪个索引; (我们看不到) 等

一些因素:

目前指数值的分布情况, MySQL 版本, 月相。

这些也可能导致EXPLAIN 中的数字(估计值)不同,从而可能导致不同的查询计划。

服务器中的其他活动也会干扰 CPU/IO/等的可用性。特别是数据的缓存很容易显示出 10 倍的差异。您是否将每个查询运行了两次?查询缓存是否关闭? innodb_buffer_pool_size 一样吗? RAM 大小是否相同?

我看到 Using index condition 并且没有“复合”索引。通常可以通过提供合适的复合索引来提高性能。 More

我要看看查询!

播种

随机或不那么随机的行会影响优化器对使用哪个索引(等)的选择。这可能导致选择了一种更好的方式在“测试”上运行查询。

我们需要看到EXPLAIN SELECT ... 来进一步讨论这个角度。

复合索引

这些可能对两台服务器都有帮助:

INDEX(facility_id, operation,   -- either order
      check_time)               -- last
INDEX(facility_id, user_id, max_address, check_time,  -- any order
      id)                       -- last

有一个快速的改进。不是查找 all 后面的行,但不使用它们的内容,而是使用“半连接”来询问 any不存在 /em> 这样的行:

SELECT  `usage_logs`.*
    FROM  `usage_logs`
    WHERE  `usage_logs`.`facility_id` = 5
      AND  `usage_logs`.`operation` = 'checkIn'
      AND  (usage_logs.check_time >= '2018-06-08 00:00:00')
      AND  (usage_logs.check_time <= '2018-06-08 11:23:05')
      AND NOT EXISTS ( SELECT 1 FROM  usage_logs AS latest 
             WHERE  usage_logs.facility_id = latest.facility_id
               AND  usage_logs.user_id     = latest.user_id
               AND  usage_logs.mac_address = latest.mac_address
               AND  usage_logs.check_time  < latest.check_time )

(相同的索引就可以了。)

查询似乎是“除了最新的”;这是你想要的吗?

【讨论】:

Groupwise max 在使用左连接和&lt; 时效率非常低。我讨论更好的方法 [here](mysql.rjweb.org/doc.php/groupwise_max)。 我试图实现这个:***.com/a/7745635/1836143 我也会读你的文章。 @AlexPopov - 好吧,尽管有大量赞成票,但该参考资料效率低下。请注意我对该问题的回答。我对 groupwise max 进行了研究,发现大多数技术效率低下。

以上是关于如何找到针对不同数据库的查询执行时间差异的原因?的主要内容,如果未能解决你的问题,请参考以下文章

学说减慢了表现

针对不同媒体查询的不同 CSS 文件

为啥针对 S3 的 pyspark sql 查询返回空值

如何找到 no_of_value 和 no_of_distinct 列值之间的差异?

JPQL 的基本使用

mybatis中批量更新的问题