【发布时间】:2012-07-17 12:41:14
【问题描述】:
关于 MySQL 中查询性能的问题。我有一个包含 230 万条记录(并且还在增长)的表(我处理过的最大的表)。该表是数据库的一部分,用于跟踪用户登录并在单独的类似测验的会话中得分。对于手头的查询,我需要所有会话的“高分表”。
因此,为了更好地分析用户的进度,每个问题都会存储会话中的得分。一个会话结合了一个用户的总积分,一个会话连接到一个用户。
起初查询执行时间接近 12 秒(不可接受),表和查询数据在“原始集”下如下所示。在“改进的分数表”下,索引中存在一些优化的变化情况。这导致查询执行时间约为 2 秒。
我的问题是:是否有其他优化方法?就像我说的,230 万(并且还在增加)是我见过的最大的表,所以我在这方面的经验并不多,而且优化在几秒钟内就会比十分之一秒的改进更快。
原集
CREATE TABLE `players` (
`id_players` int(10) unsigned NOT NULL AUTO_INCREMENT,
`id_organisations` int(10) unsigned NOT NULL,
`player_name` varchar(45) NOT NULL,
`player_comments` text NOT NULL,
PRIMARY KEY (`id_players`),
KEY `FK_players_organisation` (`id_organisations`),
CONSTRAINT `FK_players_organisation` FOREIGN KEY (`id_organisations`) REFERENCES `organisations` (`id_organisations`)
) ENGINE=InnoDB AUTO_INCREMENT=9139 DEFAULT CHARSET=latin1
SELECT COUNT(*) FROM players => 9126
CREATE TABLE `scores` (
`id_scores` int(10) unsigned NOT NULL AUTO_INCREMENT,
`id_sessions` int(10) unsigned NOT NULL,
`id_levels` int(10) unsigned NOT NULL,
`id_categories` int(10) unsigned NOT NULL,
`score_points` int(10) unsigned NOT NULL,
`score_correct` tinyint(4) NOT NULL,
`score_submitted` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id_scores`),
KEY `FK_scores_sessions` (`id_sessions`),
KEY `FK_scores_levels` (`id_levels`),
KEY `FK_scores_categories` (`id_categories`),
KEY `Index_3_points` (`score_points`),
KEY `Index_4_submitted` (`score_submitted`)
) ENGINE=InnoDB AUTO_INCREMENT=2328510 DEFAULT CHARSET=latin1
SELECT COUNT(*) FROM scores => 2328469
CREATE TABLE `sessions` (
`id_sessions` int(10) unsigned NOT NULL AUTO_INCREMENT,
`id_players` int(10) unsigned NOT NULL,
`id_classes` int(11) DEFAULT NULL,
`session_start` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`session_grade` decimal(4,1) NOT NULL,
`session_ip` varchar(45) DEFAULT NULL,
PRIMARY KEY (`id_sessions`),
KEY `FK_sessions_players` (`id_players`),
KEY `FK_sessions_classes` (`id_classes`)
) ENGINE=InnoDB AUTO_INCREMENT=40800 DEFAULT CHARSET=latin1
SELECT COUNT(*) FROM sessions => 40788
“违规”查询:
SELECT sum( s.score_points ) AS score_points, p.player_name
FROM scores s
INNER JOIN sessions se ON s.id_sessions = se.id_sessions
INNER JOIN players p ON se.id_players = p.id_players
GROUP BY se.id_sessions
ORDER BY score_points DESC
LIMIT 50;
上述分数表的查询耗时约 12 秒。 (在 EXPLAIN 输出下方)
id select_type table type possible_keys key key_len ref rows Extra
'1' 'SIMPLE' 'p' 'ALL' 'PRIMARY' NULL NULL NULL '9326' 'Using temporary; Using filesort'
'1' 'SIMPLE' 'se' 'ref' 'PRIMARY,FK_sessions_players' 'FK_sessions_players' '4' 'earzsql.p.id_players' '2' 'Using index'
'1' 'SIMPLE' 's' 'ref' 'FK_scores_sessions' 'FK_scores_sessions' '4' 'earzsql.se.id_sessions' '72' ''
(显然臭名昭著的 Using temporary 和 Using filesort)
经过一些“研究”后,我更改了分数表中的索引 (Index_3_points),结果如下表:
改进的分数表
CREATE TABLE `scores` (
`id_scores` int(10) unsigned NOT NULL AUTO_INCREMENT,
`id_sessions` int(10) unsigned NOT NULL,
`id_levels` int(10) unsigned NOT NULL,
`id_categories` int(10) unsigned NOT NULL,
`score_points` int(10) unsigned NOT NULL,
`score_correct` tinyint(4) NOT NULL,
`score_submitted` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id_scores`),
KEY `FK_scores_sessions` (`id_sessions`),
KEY `FK_scores_levels` (`id_levels`),
KEY `FK_scores_categories` (`id_categories`),
KEY `Index_4_submitted` (`score_submitted`),
KEY `Index_3_points` (`id_sessions`,`score_points`)
) ENGINE=InnoDB AUTO_INCREMENT=2328510 DEFAULT CHARSET=latin1
使用上面的分数表,查询执行时间下降到大约 2 秒。解释(下)并没有真正改变很多(至少,臭名昭著的临时和文件排序仍在使用)
id select_type table type possible_keys key key_len ref rows Extra
'1' 'SIMPLE' 'p' 'ALL' 'PRIMARY' NULL NULL NULL '9326' 'Using temporary; Using filesort'
'1' 'SIMPLE' 'se' 'ref' 'PRIMARY,FK_sessions_players' 'FK_sessions_players' '4' 'earzsql.p.id_players' '2' 'Using index'
'1' 'SIMPLE' 's' 'ref' 'FK_scores_sessions,Index_3_points' 'Index_3_points' '4' 'earzsql.se.id_sessions' '35' 'Using index'
如果有人知道进一步的优化技巧,我很想听听。
【问题讨论】:
标签: mysql sql query-optimization