MySQL查询在大型表上运行速度非常慢

时间:2011-12-02 20:47:35

标签: mysql sql

我试图在一个非常大的表上运行以下查询,其中超过9000万行增加

SELECT COUNT(DISTINCT device_uid) AS cnt,  DATE_FORMAT(time_start, '%Y-%m-%d') AS period 
FROM game_session 
WHERE account_id = -2 AND DATE_FORMAT(time_start '%Y-%m-%d') BETWEEN CURDATE() - INTERVAL 90 DAY AND CURDATE()
GROUP BY period 
ORDER BY period DESC

我有以下表结构:

CREATE TABLE `game_session` (
  `session_id` bigint(20) NOT NULL,
  `account_id` bigint(20) NOT NULL,
  `authentification_type` char(2) NOT NULL,
  `source_ip` char(40) NOT NULL,
  `device` char(50) DEFAULT NULL COMMENT 'Added 0.9',
  `device_uid` char(50) NOT NULL,
  `os` char(50) DEFAULT NULL COMMENT 'Added 0.9',
  `carrier` char(50) DEFAULT NULL COMMENT 'Added 0.9',
  `protocol_version` char(20) DEFAULT NULL COMMENT 'Added 0.9',
  `lang_key` char(2) NOT NULL DEFAULT 'en',
  `instance_id` char(100) NOT NULL,
  `time_start` datetime NOT NULL,
  `time_end` datetime DEFAULT NULL,
  PRIMARY KEY (`session_id`),
  KEY `game_account_session_fk` (`account_id`),
  KEY `lang_key_fk` (`lang_key`),
  KEY `lookup_active_session_idx` (`account_id`,`time_start`),
  KEY `lookup_finished_session_idx` (`account_id`,`time_end`),
  KEY `start_time_idx` (`time_start`),
  KEY `lookup_guest_session_idx` (`device_uid`,`time_start`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1

如何优化此功能?

感谢您的回答

6 个答案:

答案 0 :(得分:3)

DATE_FORMAT(time_start '%Y-%m-%d')听起来很贵 列上的每个计算都会减少索引的使用。您可能会为每个值运行完整的索引扫描+ DATE_FORMAT计算,而不是索引查找/范围扫描。

尝试将计算值存储在列中(或者如果mysql支持,则创建计算索引)。或者甚至更好地重写条件,直接与列中存储的值进行比较。

答案 1 :(得分:2)

嗯,90ml很多,但是我怀疑它没有使用start_time_idx因为操作,你可以避免(你可以操纵你比较它的值,它也必须完成如果mysql足够智能,每个查询只有一次),你检查过EXPLAIN吗?

答案 2 :(得分:1)

您可能希望按time_start分组和排序,而不是在运行查询时创建的period值。按period排序需要在完成任何排序之前生成所有这些值。

答案 3 :(得分:1)

尝试使用以下内容交换WHERE子句: WHERE account_id = -2 AND time_start BETWEEN CURDATE() - INTERVAL 90 DAY AND CURDATE()

MySQL仍然会捕捉到它们之间的日期,唯一需要担心的是今天的日期,由于技术上大于午夜,它们可能会被截断。

您可以通过使用CURDATE( )

递增第二个CURDATE( ) + INTERVAL 1 DAY来解决此问题

答案 4 :(得分:0)

我要改变

BETWEEN CURDATE() - INTERVAL 90 DAY AND CURDATE()

> (CURDATE() - INTERVAL 90 DAY)

你以后没有记录,是吗?

答案 5 :(得分:0)

将查询更改为:

SELECT COUNT(DISTINCT device_uid) AS cnt
     , DATE_FORMAT(time_start, '%Y-%m-%d') AS period 
FROM game_session 
WHERE account_id = -2 
  AND time_start >= CURDATE() - INTERVAL 90 DAY 
  AND time_start <  CURDATE() + INTERVAL 1 DAY
GROUP BY DATE(time_start) DESC

因此(account_id, time_start)的索引可用于查询的WHERE部分。


如果它仍然很慢 - DATE(time_start)效果不佳 - 添加date_start列并存储time_start的日期部分。

然后在(account_id, date_start, device_uid)上添加一个索引,这将进一步提高效果,因为GROUP BY date_startCOUNT(DISTINCT device_uid)部分的所有必要信息都将在索引上:

SELECT COUNT(DISTINCT device_uid) AS cnt
     , date_start                 AS period 
FROM game_session 
WHERE account_id = -2 
  AND date_start BETWEEN CURDATE() - INTERVAL 90 DAY 
                     AND CURDATE()
GROUP BY date_start DESC