[英]MySQL SUM Query is extremely slow
有一个表称为具有约600万行的transactions
。 下面的查询计算当前用户余额。 这是我启用slow_query_log = 'ON'
后的日志:
# Time: 170406 9:51:48
# User@Host: root[root] @ [xx.xx.xx.xx]
# Thread_id: 13 Schema: main_db QC_hit: No
# Query_time: 38.924823 Lock_time: 0.000034 Rows_sent: 1 Rows_examined: 773550
# Rows_affected: 0
SET timestamp=1491456108;
SELECT SUM(`Transaction`.`amount`) as total
FROM `main_db`.`transactions` AS `Transaction`
WHERE `Transaction`.`user_id` = 1008
AND `Transaction`.`confirmed` = 1
LIMIT 1;
如您所见,它花费了~38 seconds
!
这是transactions
表EXPLAIN:
该查询有时运行很快(大约1秒左右),有时却非常慢!
任何帮助将不胜感激。
PS:
它的InnoDB和transactions
表具有频繁的INSERT和SELECT操作。
我尝试使用SQL_NO_CACHE
运行查询,但有时还是很快,有时还是很慢。
transactions
表架构:
CREATE TABLE `transactions` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(10) unsigned NOT NULL,
`ref_id` varchar(40) COLLATE utf8_persian_ci NOT NULL,
`payment_id` tinyint(3) unsigned NOT NULL,
`amount` decimal(10,1) NOT NULL,
`created` datetime NOT NULL,
`private_note` varchar(6000) COLLATE utf8_persian_ci NOT NULL,
`public_note` varchar(200) COLLATE utf8_persian_ci NOT NULL,
`confirmed` tinyint(3) NOT NULL,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=13133663 DEFAULT CHARSET=utf8 COLLATE=utf8_persian_ci
MySQL在具有12GB RAM和9个逻辑CPU内核的VPS上运行。
这是my.cnf
的一部分:
# * InnoDB
#
# InnoDB is enabled by default with a 10MB datafile in /var/lib/mysql/.
# Read the manual for more InnoDB related options. There are many!
default_storage_engine = InnoDB
# you can't just change log file size, requires special procedure
innodb_buffer_pool_size = 9G
innodb_log_buffer_size = 8M
innodb_file_per_table = 1
innodb_open_files = 400
innodb_io_capacity = 400
innodb_flush_method = O_DIRECT
innodb_thread_concurrency = 0
innodb_read_io_threads = 64
innodb_write_io_threads = 64
# Instead of skip-networking the default is now to listen only on
# localhost which is more compatible and is not less secure.
#bind-address = 127.0.0.1
#
# * Fine Tuning
#
max_connections = 500
connect_timeout = 5
wait_timeout = 600
max_allowed_packet = 16M
thread_cache_size = 128
sort_buffer_size = 4M
bulk_insert_buffer_size = 16M
tmp_table_size = 32M
max_heap_table_size = 32M
(对不起,我要发表所有好的评论。我希望我已经添加足够的内容来证明要求“答案”。)
表格中是否有600万行? 但是,具有该user_id
773K行呢?
9GB buffer_pool? 该表大约有4GB的数据? 因此,如果没有其他需要解决的问题,它就适合buffer_pool。 ( SHOW TABLE STATUS
并检查“ Data_length”。)
现有的INDEX(user_id)
可能为20MB,很容易实现。
如果user_ids充分分散在表周围,则查询可能实际上需要获取数据的每个16KB块。 因此,具有原始索引的原始查询将如下所示:
user_id
的索引。 这将是总努力的一小部分。 如果更改为最佳“覆盖” INDEX(user_id, confirmed, amount)
,则情况会有所变化...
如果WHERE
子句中也有日期范围,我将推动构建和维护“摘要表”。 这样可以将类似的查询速度提高10倍。
如果您添加开头的综合指数user_id
,你应该 (不应该 ) DROP
上只是user_id说明为冗余的索引。 (如果不删除它,通常会浪费磁盘空间。)
至于在生产中...
ALTER TABLE ... ALGORITHM=INPLACE ...
,这对于添加/删除索引的影响最小。 pt-online-schema-change
。 它要求没有其他触发器,并且停机时间非常短。 触发器“透明”地处理了200次写入/分钟。 在MySQL 5.6和MariaDB 10.0中添加了ALGORITHM=INPLACE
。
(是的,我要添加另一个答案。理由:它以不同的方式解决了潜在的问题。)
潜在的问题似乎是,有一个不断增长的“交易”表,可以从中获得各种统计信息,例如SUM(amount)
。 随着表的增长,这种性能只会越来越差。
此答案的基础将以两种方式查看数据:“历史”和“当前”。 Transactions
就是历史。 一个新表将是每个用户的“ Current
总计”。 但是我看到了多种方法。 每项都涉及某种形式的小计,以避免添加773K行以获得答案。
Transactions
并将其添加到Current
。 Transactions
添加一行,都会增加Current
。 SUM
经过昨晚。 在我的摘要表博客中有更多讨论。
请注意,银行或混合方式的最新余额有些棘手:
任何方法会比扫描所有行773K为用户快得多 ,但它会更复杂的代码。
您可能要尝试的一件事是添加一个复合索引,以查看它是否可以加快查询的选择部分:
ALTER TABLE `transactions` ADD INDEX `user_confirmed` (`user_id`, `confirmed`);
另外,正如@wajeeh在评论中指出的,在这里LIMIT
子句是不必要的,因为您已经在调用聚合函数。
如果您也可以在问题中发布表架构,将很有帮助。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.