MySQL SUM查询非常慢

时间:2017-04-06 05:37:38

标签: mysql performance mysql-slow-query-log

有一个名为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:

enter image description here

此查询有时会快速运行(约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

4 个答案:

答案 0 :(得分:2)

(很抱歉踩到所有好的评论。我希望我已经添加了足够的证据来证明声明“答案”。)

表中有6M行吗?但是那个user_id的773K行?

9GB buffer_pool?该表约有4GB的数据?所以它适合在buffer_pool中,如果没有其他东西可以将其搞定。 (SHOW TABLE STATUS并检查“Data_length”。)

现有的INDEX(user_id)可能是20MB,很容易安装。

如果user_ids充分分散在表中,则查询可能需要几乎每16KB的数据块获取一次。因此,原始索引的原始查询将类似于:

  1. 扫描给定user_id的索引。这将是总努力的一小部分。
  2. 对于索引中的每个条目,查找(随机)记录。这发生了1.5M次。使用“冷”缓存,这将很容易需要38秒或更长时间。重启后不久“慢”的地方?或其他东西吹灭缓存?使用“热”缓存,它是所有CPU(无I / O),因此1秒是合理的。
  3. 如果你改为最佳,“覆盖”,INDEX(user_id, confirmed, amount),事情会改变一些......

    • “覆盖”表示整个查询将在索引中执行。 (这个复合索引可能更像是40MB,但与数据相比仍然非常小。)
    • 在“冷”缓存中,只有40MB需要获取 - 期望比38s好得多。
    • 在“热”缓存中(这次只有40MB),它可能会在半秒内运行。

    如果WHERE子句中也有日期范围,我会推动构建和维护“汇总表”。这可能会使类似的查询速度提高10倍。

    如果你确实添加了一个以user_id开头的复合索引,那么 (不是必须DROP只需将user_id作为索引多余的。 (如果你不丢弃它,它将主要浪费磁盘空间。)

    至于在生产中这样做......

    • 如果你有一个足够新的MySQL版本ALTER TABLE ... ALGORITHM=INPLACE ...,这对于添加/删除索引是可行的,而影响最小。
    • 对于旧版本,请参阅pt-online-schema-change。它要求没有其他触发器,并且确实需要非常短的停机时间。触发器“透明地”处理200次写入/分钟。
    在MySQL 5.6和MariaDB 10.0中添加了

    ALGORITHM=INPLACE

答案 1 :(得分:2)

(是的,我正在添加另一个答案。理由:它以不同的方式解决潜在的问题。)

潜在的问题似乎是有一个不断增长的交易"从中导出各种统计信息的表,例如SUM(amount)。随着表格的增长,这种情况的表现只会越来越差。

本答案的基础是以两种方式查看数据:"历史"和"当前"。 Transactions是历史。新表将是每个用户的Current总计。但我看到了多种方法。每个都涉及某种形式的小计,以避免添加773K行来得到答案。

  • 传统的银行业务方式......每天晚上都会计算Transactions并将其添加到Current
  • 物化视图方式...每次向Transactions添加一行时,请增加Current
  • 混合:将每日小计保存在"摘要表"中。总结这些小计以获得SUM到昨晚。

我的博客Summary Tables上的更多讨论。

请注意,银行或混合方式的最新余额有点棘手:

  1. 获取昨晚的金额
  2. 添加当天发生的所有交易。
  3. 任何一种方法都会比为用户扫描所有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表一样,所以这可能对你有帮助。