mysql - 100K-1M 行 500 tables 或 50-500M 行 1 tables

mysql - 500 tables with 100K-1M rows or 1 table with 50-500M rows

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

我测试了两种变体,发现:

这里唯一的问题可能是同时打开了很多 table。在阅读 phpMyAdmin 中的设置建议之前,我并不认为这是一个问题,所以现在我怀疑我是否应该使用那么多 tables?

以防万一这里有模式。 "small" table:

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

更新

我了解了索引并最终得到了以下大 table(2000 万行)的架构:

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 共享 table:

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 ) ;

我喜欢使用一个大 table 的想法,然而,虽然我在两个查询中得到相似的时间(小的 ~0,050 和大的 ~0,065),解释仍然让我有点困惑: 对于小 table

类型 |钥匙 | key_len |参考 |行 |额外

index; score; 4; (null); 50049; Using where, Using index

大 table:

ref; PRIMARY 2; const; 164030; Using where

如您所见,在小 table 中扫描的行数减少了 3 倍。所有 table 中的数据都相同,第 20 层填充了查询:

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

另一个更新

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

#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

而且我的查询在两个 table 上的工作方式几乎相同。我觉得 table 中等整数更好,我离开了,但仍然有点困惑。

你的查询有点奇怪。试试这个

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

你的 ORDER BY 在这里毫无意义,因为这个查询只能 return 一行:它是一个没有任何 GROUP BY.

的聚合查询

五百 tables 是个糟糕的主意。你的管理任务会很不愉快。

此外,对 table 进行分区对查询性能几乎没有帮助。在您提议的情况下,在 hash(id) 上进行分区肯定会破坏您所显示的查询的性能;每个查询都必须读取每个分区。

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

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

Edit MySQL 是为这种具有十亿行的四长字 table 构建的。如果您有耐心并了解索引,您 使它正常工作。不要将不可替代的时间浪费在数百个较小的 table 或分区上。不过,更多 RAM 可能会有所帮助。

对于 InnoDB 的性能来说,最好的事情是确保所有经常使用的数据都适合缓冲池。使用您发布的 table 结构,您似乎需要大约 500MB 的缓冲池 space 才能将所有数据保存在缓冲池中。

排行榜 table 的更好结构是:

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. 列表项

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

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

为了节省 space(从而使事情更容易缓存,从而更快),从 INT(4 字节)缩小到 MEDIUMINT UNSIGNED(3 字节,0-16M 范围)或更小。

CHAR(64) -- 字符串总是 64 个字符吗?如果没有,用VARCHAR(64)保存space。 (‘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 中的 "Rows" 是估计值。有时它是一个粗略的估计。

你懂的SQL;为什么要努力为 NoSQL 自己编码 "SELECT"?

分区不会自动提供任何 性能提升。而且您还没有显示任何有益的查询。

我同意 500 个类似的表是不值得的。

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

leaderboard PK -- 你是说一个user_id可以在多个levels?