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

Ter*_*ium 0 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 */
Run Code Online (Sandbox Code Playgroud)

排名查询:

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
Run Code Online (Sandbox Code Playgroud)

更新

我已经了解了索引,最终得到了大表(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;
Run Code Online (Sandbox Code Playgroud)

对于小型(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;
Run Code Online (Sandbox Code Playgroud)

具有长文字用户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;
Run Code Online (Sandbox Code Playgroud)

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

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 ) ;
Run Code Online (Sandbox Code Playgroud)

我喜欢使用一个大表的想法,但是,虽然我在两个查询中得到了类似的时间(小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;
Run Code Online (Sandbox Code Playgroud)

另一个更新

今天用表进行了实验,发现将int更改为medium 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
Run Code Online (Sandbox Code Playgroud)

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

O. *_*nes 5

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

SELECT   COUNT(*) 
  FROM   leaderboard
 WHERE   level_no = 0 and score > $current_score
Run Code Online (Sandbox Code Playgroud)

这里的ORDER BY是没有意义的,因为这个查询只能返回一行:它是一个没有任何的聚合查询 GROUP BY.

五百张桌子是一个糟糕的主意.您的管理任务将非常不愉快.

此外,对表进行分区很少有助于查询性能.在您提议的情况下,分区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;
Run Code Online (Sandbox Code Playgroud)

变化:

  1. timestampuser_id列是UNSIGNED:扩展用户ID的范围,我假设你没有使用负时间值,当前的unix时间戳高于有符号的范围.
  2. 时间戳可能更容易用作TIMESTAMP类型:TIMESTAMP使用4个字节,INT但显示为日期时间.
  3. 删除了level_no索引:它与level_no_score索引是冗余的,因为索引的前缀可以用来代替整个索引.
  4. 项目清单

(level_no, user_id)如果经常在查询中使用这些列并删除不需要的列(id),则使用主键将有所帮助.InnoDB仅在未明确定义主键时隐式创建主键,因此仅创建用作主键的id列是浪费.

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