mysql - 包含100K-1M行的500个表或包含50-500M行的1个表

时间:2015-04-15 22:03:26

标签: mysql performance mariadb

我读了很多类似的帖子,但我不明白该选择什么。 从软件角度来看,它是游戏排行榜。所有排行榜或500张小桌子的一张桌子,每个游戏级别一张?

我测试了两种变体,并找到了:

  • 1个大表的工作速度较慢(创建了所有需要的索引)。

  • 1个大表应该至少分成10个文件以保证足够的速度。

  • 500张小桌子不方便,但快两倍(50M大桌子对100K小桌子)

  • 500个小表不需要分区(我在mysql中听说过一些问题,也许在MariaDB 10.0中,我使用的一切都已修复,但以防万一)

这里唯一的问题可能是一次打开很多桌子。在phpMyAdmin中读取设置建议之前我没有认识到这是一个问题,所以现在我怀疑我应该使用那么多表吗?

以防这里的模式。 “小”表:

CREATE TABLE IF NOT EXISTS `level0` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) DEFAULT '0',
  `score` int(11) NOT NULL,
  `timestamp` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `user_id` (`user_id`),
  KEY `score` (`score`),
  KEY `timestamp` (`timestamp`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 

CREATE TABLE IF NOT EXISTS `leaderboard` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) DEFAULT '0',
  `level_no` int(11) NOT NULL,
  `score` int(11) NOT NULL,
  `timestamp` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `user_id` (`user_id`),
  KEY `level_no` (`level_no`),
  KEY `score` (`score`),
  KEY `timestamp` (`timestamp`),
  KEY `lev_sc` (`level_no`,`score`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1
/*!50100 PARTITION BY HASH (id)
PARTITIONS 10 */

排名查询:

SELECT   COUNT(score) FROM level0  WHERE score > $current_score 
ORDER BY score desc

SELECT   COUNT(score) FROM leaderboard  WHERE 
level_no = 0 and score > $current_score ORDER BY score desc

更新

我已经了解了索引,最终得到了大表(20M行)的以下模式:

CREATE TABLE IF NOT EXISTS `leaderboard` (
  `user_id` int(11) NOT NULL DEFAULT '0',
  `level_no` smallint(5) unsigned NOT NULL,
  `score` int(11) unsigned NOT NULL,
  `timestamp` int(11) unsigned NOT NULL,
  PRIMARY KEY (`level_no`,`user_id`),
  KEY `user_id` (`user_id`),
  KEY `score` (`score`),
  KEY `level_no_score` (`level_no`,`score`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

和小(100K行,从level_no = 200的排行榜获得):

CREATE TABLE IF NOT EXISTS `level20` (
  `user_id` int(11) NOT NULL DEFAULT '0',
  `score` int(11) NOT NULL,
  `timestamp` int(11) NOT NULL,
  PRIMARY KEY (`user_id`),
  KEY `score` (`score`),
  KEY `timestamp` (`timestamp`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

具有长文字用户ID的共享表:

CREATE TABLE IF NOT EXISTS `player_ids` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `store_user_id` char(64) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `store_user_id` (`store_user_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1;

对于测试,我使用了这些查询:

SELECT   COUNT(*) AS rank FROM level20 lev   WHERE score > 
  (SELECT score FROM level20 lt INNER JOIN player_ids pids ON 
    pids.id = lt.user_id WHERE pids.store_user_id='3FGTOHQN6UMwXI47IiRRMf9WI777SSJ6A' );

  SELECT   COUNT(*) AS rank FROM leaderboard lev   WHERE level_no=20 and score > 
  (SELECT score FROM leaderboard lt INNER JOIN player_ids pids ON 
    pids.id = lt.user_id WHERE pids.store_user_id='3FGTOHQN6UMwXI47IiRRMf9WI777SSJ6A'  and level_no=20 ) ;

我喜欢使用一个大表的想法,但是,虽然我在两个查询中得到了相似的时间(小0~0,050和大0~0,065),但是解释仍然让我感到困惑: 小桌子

输入|关键| key_len | ref |行|额外

  

指数;得分了; 4; (空值); 50049;使用where,使用索引

和大桌子:

  

REF;小学2;常量; 164030;使用何处

正如您所看到的,在小表中扫描的行数减少了3倍。所有表中的数据都相同,level20填充了查询:

INSERT INTO level20 (user_id, score, timestamp) SELECT user_id, score,
    timestamp FROM leaderboard WHERE level_no=20;

另一次更新

今天用表进行了实验,发现将int更改为中int几乎不会改变表的大小。这是优化后的统计数据(重新创建+分析):

#medium ints
CREATE TABLE IF NOT EXISTS `leaderboard1` (
  `user_id` mediumint(8) unsigned NOT NULL DEFAULT '0',
  `level_no` smallint(5) unsigned NOT NULL DEFAULT '0',
  `score` mediumint(8) unsigned NOT NULL DEFAULT '0',
  `timestamp` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  PRIMARY KEY (`level_no`,`user_id`),
  KEY `score` (`score`),
  KEY `level_no_score` (`level_no`,`score`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;


Data 628    Mb
Index   521.6   Mb
Total   1.1 Gb

#ints
CREATE TABLE IF NOT EXISTS `leaderboard` (
  `user_id` int(11) NOT NULL DEFAULT '0',
  `level_no` smallint(5) unsigned NOT NULL,
  `score` int(11) unsigned NOT NULL,
  `timestamp` int(11) unsigned NOT NULL,
  PRIMARY KEY (`user_id`,`level_no`),
  KEY `score` (`score`),
  KEY `level_no_score` (`level_no`,`score`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Data 670    Mb
Index   597.8Mb
Total   1.2 Gb

我的查询在两个表上的工作方式几乎相同。我有一种感觉,中等量的表更好,我离开它,但仍然有点困惑。

2 个答案:

答案 0 :(得分:5)

你的疑问有点奇怪。试试这个

SELECT   COUNT(*) 
  FROM   leaderboard
 WHERE   level_no = 0 and score > $current_score

此处的ORDER BY毫无意义,因为此查询只能返回一行:它是一个没有任何GROUP BY的聚合查询。

500张桌子是一个糟糕的主意。你的管理任务将非常不愉快。

此外,对表进行分区很少有助于查询性能。在你提议的情况下,在hash(id)上进行分区肯定会破坏你所显示的查询的性能;每个查询都必须读取每个分区。

保持简单。一张桌子。当它变得相当大时,使用EXPLAIN来分析您的查询性能,并考虑添加适当的复合索引。

不要创建不需要的索引。它们减慢了插入速度并浪费了硬盘空间。阅读此http://use-the-index-luke.com/

编辑 MySQL是为这种具有5亿行的四长字表构建的。如果您有耐心并了解索引,那么使其正常运行。不要浪费数百个小桌子或分区的不可替代的时间。但是,更多RAM可能有所帮助。

InnoDB的最佳性能是确保所有常用数据都适合缓冲池。使用您发布的表结构,看起来您需要大约500MB的缓冲池空间来保留缓冲池中的所有数据。

排行榜的更好结构是:

CREATE TABLE IF NOT EXISTS `leaderboard` (
  `user_id` INT(10) UNSIGNED NOT NULL DEFAULT '0',
  `level_no` SMALLINT(5) UNSIGNED NOT NULL,
  `score` int(10) NOT NULL,
  `timestamp` int(10) UNSIGNED NOT NULL,
  PRIMARY KEY (`level_no`,`user_id`),
  KEY `user_id` (`user_id`),
  KEY `score` (`score`),
  KEY `level_no_score` (`level_no`,`score`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

的变化:

  1. timestampuser_id列为UNSIGNED:扩展用户ID的范围,我假设您没有使用负时间值,当前的unix时间戳高于签名范围。
  2. 时间戳可能更容易用作TIMESTAMP类型:TIMESTAMP使用4个字节,如INT但显示为日期时间。
  3. 删除了level_no索引:它与level_no_score索引是多余的,因为索引的前缀可以用来代替整个索引。
  4. 列表项
  5. 如果经常在查询中使用这些列并删除不需要的列((level_no, user_id)),则使用id作为主键将有所帮助。 InnoDB仅在未明确定义主键时隐式创建主键,因此仅将id列用作主键是浪费。

    “正确”主索引还取决于数据和访问模式。表中有什么独特之处?它真的是level_nouser_id还是只是用户?如果只是user_id那可能是一个更好的主键。

答案 1 :(得分:1)

为了节省空间(因此使事物更易于缓存,因此更快),从INT(4字节)缩小到MEDIUMINT UNSIGNED(3字节,0-16M范围)或更小。

CHAR(64) - 字符串总是64个字符?如果没有,请使用VARCHAR(64)来节省空间。 ('3FGTOHQN6UMwXI47IiRRMf9WI777SSJ6A'只有33?)

对于leaderboard,我认为你可以摆脱一个索引:

PRIMARY KEY (`user_id`, `level_no`),  -- reversed
# KEY `user_id` (`user_id`),  -- not needed
KEY `score` (`score`),
KEY `level_no_score` (`level_no`,`score`)  -- takes care of any lookup by just `level_no`

重新“3x”:EXPLAIN中的“行数”是估算值。有时这是一个粗略的估计。

你知道SQL;为什么要为NoSQL自己编写“SELECT”代码?

PARTITIONing不会自动提供任何性能提升。并且您没有显示任何有益的查询。

我同意500张相似的表比实际值更麻烦。

2GB的内存?最好将innodb_buffer_pool_size保持在300M左右。交换比缩小buffer_pool更糟糕。

leaderboard PK - 您说一个user_id可以是多个levels