如何找到针对不同数据库的查询执行时间差异的原因?
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 在使用左连接和<
时效率非常低。我讨论更好的方法 [here](mysql.rjweb.org/doc.php/groupwise_max)。
我试图实现这个:***.com/a/7745635/1836143 我也会读你的文章。
@AlexPopov - 好吧,尽管有大量赞成票,但该参考资料效率低下。请注意我对该问题的回答。我对 groupwise max 进行了研究,发现大多数技术效率低下。以上是关于如何找到针对不同数据库的查询执行时间差异的原因?的主要内容,如果未能解决你的问题,请参考以下文章