我正在为我的数据库中的用户实现朋友列表,其中列表将存储朋友accountID.
我已经在我的数据库中有类似的结构,我有一个单独的表,其中有一对accountID到achievementID,但我对这种方法的关注是效率低,因为如果有100万用户,每个有100个成就,那么此表中有1亿条记录.然后尝试为具有特定accountID的用户获得每项成就将是对该表的线性扫描(我认为).
我正在考虑为我的朋友列表提供逗号分隔的accountID字符串,我意识到将数据作为字符串处理会有多烦人,但至少可以保证用户的log(n)搜索时间使用accountID作为主键,第二列作为列表字符串.
我对这两种不同结构的搜索时间有误吗?
MySQL可以有效地使用适当的索引,用于设计使用这些索引的查询,避免对表进行"扫描"操作.
如果您始终为用户处理完整的成就集,检索整个集并存储整个集,那么在单个列中以逗号分隔的列表可能是一种可行的方法.
但是,当你想要处理个人成就时,这种设计就会崩溃.例如,如果要检索具有特定成就的用户列表.现在,您正在对所有用户进行昂贵的全部扫描,执行"字符串搜索",依赖于格式正确的字符串,并且MySQL无法使用索引扫描来有效地检索该集合.
因此,经验法则,如果你从来没有需要单独访问的成就,绝不需要从用户数据库中删除的成就,绝不需要添加一个单独的成就,一个用户,你将永远只能拉成就作为整个集合,并且仅将它们作为整个集合存储在数据库内外,逗号分隔列表是可行的.
我对推荐这种方法犹豫不决,因为它从未如此.不可避免地,您需要一个查询来获取具有特定成就的用户列表.
使用逗号分隔列表列,您会遇到一些丑陋的SQL:
SELECT a.user_id
FROM user_achievement_list a
WHERE CONCAT(',',a.list,',') LIKE '%,123,%'
Run Code Online (Sandbox Code Playgroud)
在MySQL不能使用索引范围扫描来满足谓词的意义上是丑陋的; MySQL必须查看每个成就列表,然后从头到尾对它们中的每一个进行字符串扫描,以查明行是否匹配.
如果你想使用该列表中的各个值来执行连接操作,以"查找"另一个表中的一行,那么这是非常难以忍受的.SQL只是非常丑陋.
并且声明性地执行数据完整性是不可能的; 您不能定义任何限制添加到列表中的值的外键约束,也不achievement_id能从发生的每个列表中删除特定的所有匹配项.
基本上,你"放弃"关系数据存储的优势; 所以不要指望数据库能够使用该类型的列进行任何操作.就数据库而言,它只是一个数据块,也可能是存储在该列中的.jpg图像,MySQL无法帮助检索或维护该列表的内容.
另一方面,如果你使用一个设计来存储各个行,每个用户的每个成就作为一个单独的行,并且你有一个适当的索引可用,数据库在返回列表和SQL时可以更有效率更简单:
SELECT a.user_id
FROM user_achievements a
WHERE a.achievement_id = 123
Run Code Online (Sandbox Code Playgroud)
覆盖索引适用于该查询:
... ON user_achievements (achievement_id, user_id)
Run Code Online (Sandbox Code Playgroud)
user_id作为前导列的索引适用于其他查询:
... ON user_achievements (user_id, achievement_id)
Run Code Online (Sandbox Code Playgroud)
跟进
使用EXPLAIN SELECT ...地看到,MySQL的生成访问计划.
对于您的示例,检索给定用户的所有成就,MySQL可以对索引进行范围扫描,以快速定位一个用户的行集.MySQL不需要查看索引中的每个页面,索引被构造为树(至少在B-Tree索引的情况下),因此它基本上可以消除整个船载页面"知道"它你正在寻找的行不可能.并且achievement_id在索引中,MySQL可以直接从索引返回结果集,而无需访问基础表中的页面.(对于InnoDB引擎,PRIMARY KEY是表的集群键,因此表本身实际上是一个索引.)
使用两列InnoDB表(user_id, achievement_id),将这两列作为复合PRIMARY KEY,您只需要添加一个辅助索引(achievement_id, user_id).
跟进
问:通过二级索引,是指第三列包含复合(userID,achievementID)表的键.我的create table查询看起来像这样
CREATE TABLE `UserFriends`
(`AccountID` BIGINT(20) UNSIGNED NOT NULL
,`FriendAccountID` BIGINT(20) UNSIGNED NOT NULL
,`Key` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT
, PRIMARY KEY (`Key`)
, UNIQUE KEY `AccountID` (`AccountID`, `FriendAccountID`)
);
Run Code Online (Sandbox Code Playgroud)
答:不,我不是指增加第三列.如果表中只有两列是另一个表的外键(看起来它们引用同一个表,并且列都是NOT NULL并且列的组合有一个UNIQUE约束......并且在表上没有其他属性,我会考虑不使用代理作为主键.我会将UNIQUE KEY作为PRIMARY KEY.
就个人而言,我会使用InnoDB,并innodb_file_per_table启用该选项.我的表定义看起来像这样:
CREATE TABLE user_friend
( account_id BIGINT(20) UNSIGNED NOT NULL COMMENT 'PK, FK ref account.id'
, friend_account_id BIGINT(20) UNSIGNED NOT NULL COMMENT 'PK, FK ref account.id'
, PRIMARY KEY (account_id, friend_account_id)
, UNIQUE KEY user_friend_UX1 (friend_account_id, account_id)
, CONSTRAINT FK_user_friend_user FOREIGN KEY (account_id)
REFERENCES account (id) ON UPDATE CASCADE ON DELETE CASCADE
, CONSTRAINT FK_user_friend_friend FOREIGN KEY (friend_account_id)
REFERENCES account (id) ON UPDATE CASCADE ON DELETE CASCADE
) Engine=InnoDB;
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
731 次 |
| 最近记录: |