【问题标题】:Mysql InnoDb is very slow on SELECT queryMysql InnoDb 在 SELECT 查询上非常慢
【发布时间】:2016-11-08 10:16:29
【问题描述】:

我有一个mysql表,结构如下:

mysql> show create table logs \G;

Create Table: CREATE TABLE `logs` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `request` text,
  `response` longtext,
  `msisdn` varchar(255) DEFAULT NULL,
  `username` varchar(255) DEFAULT NULL,
  `shortcode` varchar(255) DEFAULT NULL,
  `response_code` varchar(255) DEFAULT NULL,
  `response_description` text,
  `transaction_name` varchar(250) DEFAULT NULL,
  `system_owner` varchar(250) DEFAULT NULL,
  `request_date_time` datetime DEFAULT NULL,
  `response_date_time` datetime DEFAULT NULL,
  `comments` text,
  `user_type` varchar(255) DEFAULT NULL,
  `channel` varchar(20) DEFAULT 'WEB',

  /**

  other columns here....

  other 18 columns here, with Type varchar and Text

  **/

  PRIMARY KEY (`id`),
  KEY `transaction_name` (`transaction_name`) USING BTREE,
  KEY `msisdn` (`msisdn`) USING BTREE,
  KEY `username` (`username`) USING BTREE,
  KEY `request_date_time` (`request_date_time`) USING BTREE,
  KEY `system_owner` (`system_owner`) USING BTREE,
  KEY `shortcode` (`shortcode`) USING BTREE,
  KEY `response_code` (`response_code`) USING BTREE,
  KEY `channel` (`channel`) USING BTREE,
  KEY `request_date_time_2` (`request_date_time`),
  KEY `response_date_time` (`response_date_time`)
) ENGINE=InnoDB AUTO_INCREMENT=59582405 DEFAULT CHARSET=utf8

它有超过 30000000 条记录。

mysql> select count(*) from logs;
+----------+
| count(*) |
+----------+
| 38962312 |
+----------+
1 row in set (1 min 17.77 sec)

现在的问题是它很慢,select的结果需要很长时间才能从表中获取记录。

我的以下子查询需要将近 30 分钟才能获取一天的记录:

    SELECT 
    COUNT(sub.id) AS count,
    DATE(sub.REQUEST_DATE_TIME) AS transaction_date,
    sub.SYSTEM_OWNER,
    sub.transaction_name,
    sub.response,
    MIN(sub.response_time),
    MAX(sub.response_time),
    AVG(sub.response_time),
    sub.channel
FROM
    (SELECT 
        id,
            REQUEST_DATE_TIME,
            RESPONSE_DATE_TIME,
            TIMESTAMPDIFF(SECOND, REQUEST_DATE_TIME, RESPONSE_DATE_TIME) AS response_time,
            SYSTEM_OWNER,
            transaction_name,
            (CASE
                WHEN response_code IN ('0' , '00', 'EIL000') THEN 'Success'
                ELSE 'Failure'
            END) AS response,
            channel
    FROM
        logs
    WHERE
        response_code != ''
            AND DATE(REQUEST_DATE_TIME) BETWEEN '2016-10-26 00:00:00' AND '2016-10-27 00:00:00'
            AND SYSTEM_OWNER != '') sub
GROUP BY DATE(sub.REQUEST_DATE_TIME) , sub.channel , sub.SYSTEM_OWNER , sub.transaction_name , sub.response
ORDER BY DATE(sub.REQUEST_DATE_TIME) DESC , sub.SYSTEM_OWNER , sub.transaction_name , sub.response DESC;

我还为我的表添加了索引,但仍然很慢。

任何帮助我怎样才能使它快速?

编辑: 使用 EXPLAIN 运行上述查询

+----+-------------+------------+------+----------------------------+------+---------+------+----------+---------------------------------+
| id | select_type | table      | type | possible_keys              | key  | key_len | ref  | rows     | Extra                           |
+----+-------------+------------+------+----------------------------+------+---------+------+----------+---------------------------------+
|  1 | PRIMARY     | <derived2> | ALL  | NULL                       | NULL | NULL    | NULL | 16053297 | Using temporary; Using filesort |
|  2 | DERIVED     | logs       | ALL  | system_owner,response_code | NULL | NULL    | NULL | 32106592 | Using where                     |
+----+-------------+------------+------+----------------------------+------+---------+------+----------+---------------------------------+

【问题讨论】:

  • 您需要更好地查看您的索引。我想这需要一些时间来弄清楚 - 你将需要它们跨越多个列,因为你有这么多 where/group/order 标准。我假设您已经运行了 EXPLAIN 来查看它在处理什么问题?
  • 您可以尝试的一件事是将AND DATE(REQUEST_DATE_TIME) BETWEEN '2016-10-26 00:00:00' AND '2016-10-27 00:00:00' 更改为AND REQUEST_DATE_TIME&gt;'2016-10-26' AND REQUEST_DATE_TIME&lt;'2016-10-27'
  • 另外值得注意的是,默认情况下,InnoDB 没有分配太多资源。你知道你的利用率是多少吗?
  • @calcinai 更新了我的帖子并添加了 EXPLAIN 的输出
  • @ChrisLear 我已经尝试在 request_date_time 中使用 > 和 REQUEST_DATE_TIME>'2016-10-26 15:05:55' AND REQUEST_DATE_TIME<'2016-10-27 20:25:15',这也可以吗?

标签: mysql performance innodb


【解决方案1】:

就目前而言,查询必须扫描整个表。

但首先,让我们公布一个可能的错误:

AND DATE(REQUEST_DATE_TIME) BETWEEN '2016-10-26 00:00:00'
                                AND '2016-10-27 00:00:00'

为您提供 两天 天的日志 - 所有 26 日和 27 日所有。或者那是你真正想要的? (BETWEEN包含。)

但性能问题是不会使用索引,因为request_date_time 隐藏在函数中(DATE)。

跳到更好的表达方式:

AND REQUEST_DATE_TIME >= '2016-10-26'
AND REQUEST_DATE_TIME  < '2016-10-26' + INTERVAL 1 DAY
  • DATETIME 可以与日期进行比较。
  • 包括 26 日凌晨,但不包括 27 日午夜。
  • 您可以轻松地将 1 更改为您希望的任意天数 - 无需处理闰日等。
  • 此公式允许在request_date_time 上使用索引,从而大大减少了要扫描的数据量。

至于其他诱人的地方:

  • != 没有很好地优化,因此没有“复合”索引可能是有益的。
  • 由于我们无法真正超越WHERE,因此没有索引对GROUP BYORDER BY 有用。
  • 我在WHERE 中关于DATE() 的cmets 不适用于GROUP BY;无需更改。

为什么要有子查询?我认为它可以在单层中完成。这将消除一个相当大的临时表。 (是的,这意味着 TIMESTAMPDIFF() 的 3 次使用,但这可能比临时表便宜很多。)

多少内存? innodb_buffer_pool_size的值是多少?

如果我的 cmets 不够用,并且如果您经常运行这样的查询(超过一天或超过一个日期范围),那么我们可以讨论构建和维护一个 Summary table,这可能会给您带来 10 倍的加速.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-09-29
    • 1970-01-01
    • 1970-01-01
    • 2021-07-11
    • 1970-01-01
    • 2015-03-18
    • 2011-09-02
    相关资源
    最近更新 更多