我读了很多类似的帖子,但我不明白该选择什么。 从软件角度来看,它是游戏排行榜。所有排行榜或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
我的查询在两个表上的工作方式几乎相同。我有一种感觉,中等量的表更好,我离开它,但仍然有点困惑。
答案 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;
的变化:
timestamp
和user_id
列为UNSIGNED
:扩展用户ID的范围,我假设您没有使用负时间值,当前的unix时间戳高于签名范围。TIMESTAMP
类型:TIMESTAMP
使用4个字节,如INT
但显示为日期时间。level_no
索引:它与level_no_score
索引是多余的,因为索引的前缀可以用来代替整个索引。如果经常在查询中使用这些列并删除不需要的列((level_no, user_id)
),则使用id
作为主键将有所帮助。 InnoDB仅在未明确定义主键时隐式创建主键,因此仅将id
列用作主键是浪费。
“正确”主索引还取决于数据和访问模式。表中有什么独特之处?它真的是level_no
和user_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
?