有一个名为transactions
的表,行数约为600万。以下查询计算当前用户余额。这是我启用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秒钟),有时甚至会很慢!
任何帮助都会非常感激。
P.S:
它的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
答案 0 :(得分:2)
(很抱歉踩到所有好的评论。我希望我已经添加了足够的证据来证明声明“答案”。)
表中有6M行吗?但是那个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次写入/分钟。 ALGORITHM=INPLACE
。
答案 1 :(得分:2)
(是的,我正在添加另一个答案。理由:它以不同的方式解决潜在的问题。)
潜在的问题似乎是有一个不断增长的交易"从中导出各种统计信息的表,例如SUM(amount)
。随着表格的增长,这种情况的表现只会越来越差。
本答案的基础是以两种方式查看数据:"历史"和"当前"。 Transactions
是历史。新表将是每个用户的Current
总计。但我看到了多种方法。每个都涉及某种形式的小计,以避免添加773K行来得到答案。
Transactions
并将其添加到Current
。Transactions
添加一行时,请增加Current
。SUM
到昨晚。我的博客Summary Tables上的更多讨论。
请注意,银行或混合方式的最新余额有点棘手:
任何一种方法都会比为用户扫描所有773K行更快 ,但代码会更复杂。
答案 2 :(得分:1)
您可能尝试的一件事是添加一个复合索引,以查看它是否加快了查询的选择部分:
ALTER TABLE `transactions` ADD INDEX `user_confirmed` (`user_id`, `confirmed`);
另外,正如@wajeeh在评论中指出的那样,LIMIT
子句在这里是不必要的,因为你已经调用了聚合函数。
如果您也可以在问题中发布表格架构,那将会很有帮助。
答案 3 :(得分:0)
看一下这个答案Any way to select without causing locking in MySQL?
这篇文章:Consistent Nonlocking Reads
在我认为和@billynoah提到的情况下,表必须执行许多写操作,就像Log
表一样,所以这可能对你有帮助。