MySQL数据库中的逗号分隔列表
我正在为数据库中的用户实现一个好友列表,该列表将存储好友帐户ID 在我的成就数据库中,我已经有了一个类似的结构,其中我有一个单独的表,其中有一对accountID to AchieventId,但我对这种方法的担心是,它效率低下,因为如果有100万用户每个都有100个成就,那么这个表中就有1亿个条目。然后,试图为具有特定accountID的用户获得每一项成就将是对表的线性扫描(我认为) 我正在考虑为我的好友列表表设置一个逗号分隔的accountID字符串,我意识到将数据作为字符串处理是多么烦人,但至少可以保证用户使用accountID作为主键,第二列作为列表字符串时的日志(n)搜索时间MySQL数据库中的逗号分隔列表,mysql,database,Mysql,Database,我正在为数据库中的用户实现一个好友列表,该列表将存储好友帐户ID 在我的成就数据库中,我已经有了一个类似的结构,其中我有一个单独的表,其中有一对accountID to AchieventId,但我对这种方法的担心是,它效率低下,因为如果有100万用户每个都有100个成就,那么这个表中就有1亿个条目。然后,试图为具有特定accountID的用户获得每一项成就将是对表的线性扫描(我认为) 我正在考虑为我的好友列表表设置一个逗号分隔的accountID字符串,我意识到将数据作为字符串处理是多么烦人,
对于这两种不同结构的搜索时间,我错了吗?MySQL可以有效地使用适当的索引,用于设计为使用这些索引的查询,避免对表进行“扫描”操作 如果您总是为用户处理一整套成就,检索整个成就集并存储整个成就集,那么在单个列中使用逗号分隔的列表可能是一种可行的方法 然而……当你想处理个人成就时,这种设计就失败了。例如,如果要检索具有特定成就的用户列表。现在,你要对所有用户的所有成就进行昂贵的全面扫描,进行“字符串搜索”,依赖于格式正确的字符串,而MySQL无法使用索引扫描有效地检索该集合 因此,根据经验法则,如果您从不需要单独访问一项成就,从不需要从数据库中的用户中删除一项成就,从不需要为用户添加一项个人成就,那么您将仅将这些成就作为一个整体拉取,并且只将它们作为一个完整的集合存储在数据库内外,逗号分隔列表是可行的
我不太愿意推荐这种方法,因为它永远不会变成那样。不可避免地,您需要一个查询来获得具有特定成就的用户列表 使用逗号分隔的列表列,您会陷入一些难看的SQL:
SELECT a.user_id
FROM user_achievement_list a
WHERE CONCAT(',',a.list,',') LIKE '%,123,%'
丑陋的是MySQL不能使用索引范围扫描来满足谓词;MySQL必须查看每个成就列表,然后从头到尾对每个成就进行字符串扫描,以确定一行是否匹配
如果您想使用该列表中的单个值来执行联接操作,或者“查找”另一个表中的行,这将是非常痛苦的。这种SQL变得非常丑陋
数据完整性的声明性实施是不可能的;您不能定义任何外键约束来限制添加到列表中的值,也不能从每个列表中删除特定成就id
基本上,您正在“放弃”关系数据存储的优势;因此,不要期望数据库能够处理这种类型的列。就数据库而言,它只是一个数据块,也可能是存储在该列中的.jpg图像,MySQL不会帮助检索或维护该列表的内容
另一方面,如果您采用存储各行的设计,每个用户的每个成就都作为一个单独的行存储,并且您有一个适当的可用索引,那么数据库返回列表的效率会更高,SQL也更简单:
SELECT a.user_id
FROM user_achievements a
WHERE a.achievement_id = 123
覆盖索引适用于该查询:
... ON user_achievements (achievement_id, user_id)
以user\u id
作为前导列的索引适用于其他查询:
... ON user_achievements (user_id, achievement_id)
跟进 使用
EXPLAIN-SELECT…
查看MySQL生成的访问计划
例如,在检索给定用户的所有成就时,MySQL可以对索引进行范围扫描,以快速找到一个用户的行集。MySQL不需要查看索引中的每个页面,索引是以树的形式构造的(至少在B-tree索引的情况下是这样),因此它基本上可以消除一大堆它“知道”您要查找的行不可能存在的页面。通过索引中的eaccessment\u id
,MySQL可以直接从索引返回结果集,而无需访问基础表中的页面。(对于InnoDB引擎,主键是表的集群键,因此表本身实际上是一个索引。)
使用两列InnoDB表(用户id,成就id)
,将这两列作为复合主键,只需在(成就id,用户id)
上添加一个二级索引
跟进
Q:次级索引指的是第三列,其中包含复合表(userID,AchieventId)的键。我的createtable查询如下所示
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`)
);
A:不,我不是说增加第三列。如果表中只有两列是另一个表的外键(看起来它们指的是同一个表,并且这些列都不是空的,并且对列的组合有一个独特的约束……并且表上没有其他属性,我将考虑不使用代理作为主键。我将把唯一的密钥设为主键。
就个人而言,我将使用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;
MySQL可以有效地使用适当的索引,用于设计为使用这些索引的查询,避免对t进行“扫描”操作