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,每个游戏级别一个?
我测试了两种变体,发现:
1 big table 工作速度较慢(创建了所有需要的索引)。
1 个大 table 应该至少分成 10 个文件以保证足够的速度。
500 个小 table 不是那么方便,但速度快了一倍(50M 大 table vs 100K 小 table)
500 small tables 不需要分区(我在 mysql 中听说它有一些问题,也许在我使用的 MariaDB 10.0 中一切都是固定的,但是以防万一)
这里唯一的问题可能是同时打开了很多 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;
更改:
timestamp
和 user_id
列是 UNSIGNED
:扩展用户 ID 的范围,我假设您没有使用负时间值并且当前的 unix 时间戳在有符号范围之上。
- 时间戳可能更易于用作
TIMESTAMP
类型:TIMESTAMP
使用 4 个字节,如 INT
但显示为日期时间。
- 删除了
level_no
索引:它与 level_no_score
索引是多余的,因为可以使用索引前缀代替整个索引。
- 列表项
如果您经常在查询中使用这些列并删除不需要的列 (id
),则使用 (level_no, user_id)
作为主键会有所帮助。 InnoDB 仅在未明确定义主键时才会隐式创建主键,因此创建 id
列仅用作主键是一种浪费。
"correct" 主索引还取决于数据和访问模式。 table有什么独特之处?它真的是 level_no
和 user_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
?
我看了很多类似的帖子,但我不知道该选择什么。 从软件的角度来看,它是游戏排行榜。所有排行榜一个 table 或 500 个小 table,每个游戏级别一个?
我测试了两种变体,发现:
1 big table 工作速度较慢(创建了所有需要的索引)。
1 个大 table 应该至少分成 10 个文件以保证足够的速度。
500 个小 table 不是那么方便,但速度快了一倍(50M 大 table vs 100K 小 table)
500 small tables 不需要分区(我在 mysql 中听说它有一些问题,也许在我使用的 MariaDB 10.0 中一切都是固定的,但是以防万一)
这里唯一的问题可能是同时打开了很多 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;
更改:
timestamp
和user_id
列是UNSIGNED
:扩展用户 ID 的范围,我假设您没有使用负时间值并且当前的 unix 时间戳在有符号范围之上。- 时间戳可能更易于用作
TIMESTAMP
类型:TIMESTAMP
使用 4 个字节,如INT
但显示为日期时间。 - 删除了
level_no
索引:它与level_no_score
索引是多余的,因为可以使用索引前缀代替整个索引。 - 列表项
如果您经常在查询中使用这些列并删除不需要的列 (id
),则使用 (level_no, user_id)
作为主键会有所帮助。 InnoDB 仅在未明确定义主键时才会隐式创建主键,因此创建 id
列仅用作主键是一种浪费。
"correct" 主索引还取决于数据和访问模式。 table有什么独特之处?它真的是 level_no
和 user_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
?