【发布时间】:2012-11-30 13:35:32
【问题描述】:
我正在尝试解决 MySQL 的性能问题,因此我想创建一个较小版本的表来使用。当我在查询中添加 LIMIT 子句时,它会从大约 2 秒(对于完整插入)变为天文数字(42 分钟)。
mysql> select pr.player_id, max(pr.insert_date) as insert_date from player_record pr
inner join date_curr dc on pr.player_id = dc.player_id where pr.insert_date < '2012-05-15'
group by pr.player_id;
+------------+-------------+
| 1002395119 | 2012-05-14 |
...
| 1002395157 | 2012-05-14 |
| 1002395187 | 2012-05-14 |
| 1002395475 | 2012-05-14 |
+------------+-------------+
105776 rows in set (2.19 sec)
mysql> select pr.player_id, max(pr.insert_date) as insert_date from player_record pr
inner join date_curr dc on pr.player_id = dc.player_id where pr.insert_date < '2012-05-15'
group by pr.player_id limit 1;
+------------+-------------+
| player_id | insert_date |
+------------+-------------+
| 1000000080 | 2012-05-14 |
+------------+-------------+
1 row in set (42 min 23.26 sec)
mysql> describe player_record;
+------------------------+------------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------------------+------------------------+------+-----+---------+-------+
| player_id | int(10) unsigned | NO | PRI | NULL | |
| insert_date | date | NO | PRI | NULL | |
| xp | int(10) unsigned | YES | | NULL | |
+------------------------+------------------------+------+-----+---------+-------+
17 rows in set (0.01 sec) (most columns removed)
player_record 表中有 2000 万行,因此我在内存中为我要比较的特定日期创建了两个表。
CREATE temporary TABLE date_curr
(
player_id INT UNSIGNED NOT NULL,
insert_date DATE,
PRIMARY KEY player_id (player_id, insert_date)
) ENGINE=MEMORY;
INSERT into date_curr
SELECT player_id,
MAX(insert_date) AS insert_date
FROM player_record
WHERE insert_date BETWEEN '2012-05-15' AND '2012-05-15' + INTERVAL 6 DAY
GROUP BY player_id;
CREATE TEMPORARY TABLE date_prev LIKE date_curr;
INSERT into date_prev
SELECT pr.player_id,
MAX(pr.insert_date) AS insert_date
FROM player_record pr
INNER join date_curr dc
ON pr.player_id = dc.player_id
WHERE pr.insert_date < '2012-05-15'
GROUP BY pr.player_id limit 0,20000;
date_curr 有 216k 条目,如果我不使用限制,date_prev 有 105k 条目。
这些表只是流程的一部分,用于将另一个表(5 亿行)缩减为可管理的内容。 date_curr 包含当前周的 player_id 和 insert_date,date_prev 包含当前周之前的 player_id 和最近的 insert_date,用于 date_curr 中存在的任何 player_id。
这里是解释输出:
mysql> explain SELECT pr.player_id,
MAX(pr.insert_date) AS insert_date
FROM player_record pr
INNER JOIN date_curr dc
ON pr.player_id = dc.player_id
WHERE pr.insert_date < '2012-05-15'
GROUP BY pr.player_id
LIMIT 0,20000;
+----+-------------+-------+-------+---------------------+-------------+---------+------+--------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------------+-------------+---------+------+--------+----------------------------------------------+
| 1 | SIMPLE | pr | range | PRIMARY,insert_date | insert_date | 3 | NULL | 396828 | Using where; Using temporary; Using filesort |
| 1 | SIMPLE | dc | ALL | PRIMARY | NULL | NULL | NULL | 216825 | Using where; Using join buffer |
+----+-------------+-------+-------+---------------------+-------------+---------+------+--------+----------------------------------------------+
2 rows in set (0.03 sec)
这是在具有 24G RAM 专用于数据库的系统上,目前几乎处于空闲状态。这个特定的数据库是测试,所以它是完全静态的。我重新启动了 mysql,它仍然具有相同的行为。
这里是“show profile all”的输出,大部分时间都花在复制到 tmp 表上。
| Status | Duration | CPU_user | CPU_system | Context_voluntary | Context_involuntary | Block_ops_in | Block_ops_out | Messages_sent | Messages_received | Page_faults_major | Page_faults_minor | Swaps | Source_function | Source_file | Source_line |
| Copying to tmp table | 999.999999 | 999.999999 | 0.383941 | 110240 | 18983 | 16160 | 448 | 0 | 0 | 0 | 43 | 0 | exec | sql_select.cc | 1976 |
【问题讨论】:
-
我怀疑你的问题是文件排序,但我更担心查询在做什么。例如,您将行添加到 date_curr,然后将行添加到 date_prev。然后在您的查询中,您从 date_curr 中选择并且从不在查询中使用 date_prev?请稍微澄清一下这个例子。另外,该程序的哪一部分花费时间最长?是选择吗?是插页吗?请澄清。
-
show create table player_record怎么样,这样我们就可以看到索引等所有其他内容。我敢打赌,如果您在要查询的日期列上添加索引会加快速度。 -
我在 insert_date 上有一个索引,它是解释语句中使用的键。无限制的查询2秒内返回(需要组装完整的响应);我不知道限制输出比提供整个输出要慢多少。 date_curr 采用当前周的 player_record 条目,date_prev 采用当前周之前的最新 player_record。 date_prev 查询(有限制)是一个慢的查询,它和不做插入的独立查询一样慢。
-
您似乎严重过早地进行了优化。如果您只有 2000 万条记录,如果索引正确,您应该能够在表中进行最合理的工作。 (当人们开始使用临时表时,您也会遇到这种窘境。)您的表足够简单,即使 500MM 行也应该可以管理。使用 SQL 的“分而治之”可能会使读写变得更容易,但总是需要更长的时间。只要您尊重它,您就永远不会像 SQL 查询优化器那样擅长它。
-
@Josh 你的表在 playerid 和 insert_date 上有一个主键,对吗?然后你又索引了 insert_date ?如果我是正确的,那可能就是问题所在。我赞成删除 insert_date 上的索引并再次运行查询 MySQL 可能在这里使用了错误的索引。这可能是因为这是一个静态数据库,而 MySQL 可能有错误的统计数据。
标签: mysql performance limit