【问题标题】:mysql - 500 tables with 100K-1M rows or 1 table with 50-500M rowsmysql - 500 个具有 100K-1M 行的表或 1 个具有 50-500M 行的表
【发布时间】:2015-06-22 02:13:15
【问题描述】:

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

我已经测试了这两种变体,并发现:

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

  • 1 个大表应至少分区为 10 个文件以获得足够的速度。

  • 500 个小表没那么方便,但快了一倍(50M 大表 vs 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,050 和大约 0,065),但解释仍然让我有些困惑: 适合小桌子

类型 |关键 | key_len |参考 |行 |额外的

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

对于大桌子:

参考;小学二年级;常量; 164030;在哪里使用

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

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

另一个更新

今天对表格进行了实验,发现将 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

我的查询在两个表上的工作方式几乎相同。我觉得中等整数的表更好,我离开了它,但还是有点困惑。

【问题讨论】:

  • 现在如果你想搜索你所在的所有记录来寻找一个额外有趣的选择语句......
  • 有任何关系吗?如果没有,请考虑使用 MongoDB、其他 NoSQL 或其他形式的 JSON 存储可能会比 MySQL 更快。
  • @DanWhite 它与配置文件表、用户 ID 表(每个 64 个符号)、统计事件表、GCM 表等有关。当然它们可以单独存在,但我更喜欢在一台服务器上。而且我不知道MongoDB。也许 MariaDB/mysql 中没有 nosql 表引擎?还是 nosql 完全意味着另一个软件平台?
  • @JoshuaByer 是的,但是我一次需要所有表的唯一地方是 cron-launched 缓存任务“选择 user_id,从 t0 order by score limit 50 union all .... select user_id ,从 t500 开始按分数限制 50 排序。但它非常快,每小时发生一次。
  • 您是如何决定需要对大表进行分区的?基于结构和查询,这样的分区将无济于事,并且可能会减慢查询速度。大表中的level_no 索引是多余的,因为(level_no, score) 上也有一个索引。如果行在级别和用户上是唯一的,则最好分别使用(user_id)(level_no, user_id) 作为小型表和大型表的主键。您的时间戳列也应该是无符号的。

标签: mysql performance mariadb


【解决方案1】:

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

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

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

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

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

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

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

编辑 MySQL 是为这种具有 50 亿行的四长字表构建的。如果您有耐心并了解索引,您 完成这项工作。不要在数百个较小的表或分区上浪费您不可替代的时间。不过,更多的 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 类型:TIMESTAMPINT 一样使用 4 个字节,但显示为日期时间。
  3. 删除了level_no 索引:它与level_no_score 索引是多余的,因为可以使用索引的前缀来代替整个索引。
  4. 列表项

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

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

【讨论】:

  • 是的,我知道现在订购是没有意义的,谢谢。并且 - 我试图保持简单,但在具有 2GB Ram 和 2x2.4Ghz xeon 内核的 VDS 上的 50M 行看起来有点慢。当我把它分成 500 个部分时,它变得更快了。但是好吧,你让我重新思考建筑。我明天做实验,然后写下我的结果。
  • 我已经完成了一些测试并编辑了我的问题。请看一看。
  • @G_Nugget,感谢您的澄清,我的结果几乎相同,unsigned int 更好,但signed 也足够了。是的,(level_no, user_id) 是唯一的,所以是最好的PK。
【解决方案2】:

为了节省空间(因此使内容更易于缓存,因此速度更快),从 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`

Re "3x": EXPLAIN 中的 "Rows" 是一个估计值。有时这是一个粗略的估计。

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

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

我同意 500 个相似的表比它的价值更麻烦。

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

leaderboardPK -- 你是说一个user_id可以在多个levels中?

【讨论】:

  • 0。我不希望限制 1600 万用户——以防万一。感谢您的索引建议,我将对其进行测试。 1.当我读到 char 比 varchar 更快,它对 DB 更“方便”。例如,WP8 上的某些 id 将超过 33 个符号。 2.我觉得nosql不适合我。或者你是什么意思? 3. 是的,我拒绝分区和小表。 4. mysql和其他人建议将innodb_buffer_pool_size设置为内存的70-80%,所以我设置为1,4gb。为什么是300m? 5. 表中每个用户有 200-500 行,这就是为什么我将用户 ID 移动到单独的表中。
  • CHAR 胜过VARCHAR 是老生常谈;从你的脑海中抹去它。 CHAR(64)(和latin1)占用64字节; VARCHAR(...) 只有 33 个字符,占用 35 个字节。更小 -> 更快。​​
  • 70%-80% 的建议适用于现代大小的机器 (> 4GB),适用于 2GB 的虚拟机。其他东西(操作系统、mysqld 代码、其他缓冲区等)占用了大量空间,可能是 1GB。当总量超过 2GB 时,就会发生“交换”。与较小的 buffer_pool 相比,交换对性能的影响很多
  • 你有这些报价的链接吗?我想针对文档提交错误报告。它可能指的是到 MyISAM,并且可能可以追溯到近 20 年前。对于一个 small MyISAM 表,您可以在其中执行大量删除和插入操作,让整个表的长度固定是一个优势。但是任何 var 都会破坏优势;你应该使用 InnoDB;对于更大的表,I/O 通常更重要。
  • 当然,您可以独家访问 2GB。但它不仅仅是 MySQL 数据,它也是 mysql 代码等。代码吃了一大块 fixed-size 块。当他们为您配置 VM 时,他们没有为您提供操作系统选择吗?我相信操作系统会占用 2GB 的另一个 fixed-size 块。 可用 ram 的 70% 并不多。您可以尝试将 buffer_pool 增加到 3G 以强制交换;看看会发生什么。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-10-27
  • 1970-01-01
  • 1970-01-01
  • 2013-02-14
  • 2012-12-16
  • 2019-06-09
  • 1970-01-01
相关资源
最近更新 更多