0

A question about query performance in MySQL. I have a table (the largest I've ever dealt with) of 2.3 million records (and growing). The table is part of a database keeping track of users logging in and scoring points in kind of seperate, quiz-like, sessions. For the query at hand I need the 'highscore table' of all the sessions.

So, the points scored in a session are stored per question in order to analyse the progress of the user better. A session combines the total of a user's points, and a session is connected to a user.

At first the query executiontime ran towards 12 seconds (unacceptable) with the table and query data as follows under 'Original set'. Under 'Improved scores table' there is the altered situation with some optimization in the indexes. This results in a query execution time of about 2 seconds.

My Question is: Is there an additional way to optimize? Like I said, 2.3 million (and counting) is the largest table I've ever seen, so I'm not that experienced at this and optimization sooner results in seconds than tenths of a second improvement.

Original set

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

The 'offending' query:

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;

Above query took about 12 seconds with said scores table. (below the EXPLAIN ouput)

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'     ''

(the apparently infamous Using temporary and Using filesort)

After some 'research' I've changed indexes (Index_3_points) in scores table resulting in a table like:

Improved scores table

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

With above scores table the query execution time drops to about 2 seconds. Explain (below) has not really changed a lot though (at least, the infamous temporary and filesorts are still used)

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'

I'd love to hear it if anyone knows further optimization tricks.

4

1 回答 1

0

大概前 50 名的分数不会经常变化?

因此,将查询运行到 TopScore 表中,并为其编制索引。当用户的分数发生变化时,对照高分表进行检查,如果用户的分数高于第 50 位,则仅更新 TopScore 表。

我还建议向频繁更新的表添加大量索引可能会对该表产生不利的性能影响。

于 2012-07-17T13:18:34.443 回答