在我的MySQL数据库支持的web应用程序中,我想提供一个消息系统,将消息分组为多个用户之间的对话,但我一直在设计一个满足我一些需求的表结构:
- 多个用户可以参与一个对话
- 用户可以加入、阅读、离开和删除对话
- 收件箱视图应生成尽可能少的查询
现在,多对多关系的第一个要求可以通过使用连接表来解决。但事实证明,在为收件箱视图编写选择查询时,它会产生很多问题。
第二项要求也被证明是一项挑战。如果用户离开了一个对话,他应该仍然可以阅读旧消息。对话中其余用户之间的新消息不应与离开的用户共享。我首先想到的是使用树状结构进行对话。每次用户加入或离开会话时,都会创建一个新的会话,其中引用父会话,并创建与连接表中其余参与者的新关系。
第三个要求似乎也并非微不足道。收件箱视图应显示与作为参与者的特定用户的对话列表。此外,还应为每个对话显示附加信息:所有当前参与者的姓名、对对话的最后回复以及该回复的作者。将收件箱视图视为一个邮件列表,其中包含有关所属对话的附加信息。
我目前的方法是这样的:
CREATE TABLE `conversation` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`parentId` int(11) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `parentId` (`parentId`),
CONSTRAINT `conversation_ibfk_1` FOREIGN KEY (`parentId`) REFERENCES `conversation` (`id`)
);
CREATE TABLE `participant` (
`userId` int(11) unsigned NOT NULL,
`conversationId` int(11) unsigned NOT NULL,
`readAt` datetime DEFAULT NULL,
PRIMARY KEY (`userId`,`conversationId`),
KEY `conversationId` (`conversationId`),
CONSTRAINT `participant_ibfk_2` FOREIGN KEY (`conversationId`) REFERENCES `conversation` (`id`),
CONSTRAINT `participant_ibfk_1` FOREIGN KEY (`userId`) REFERENCES `user` (`id`)
);
CREATE TABLE `reply` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`conversationId` int(11) unsigned NOT NULL,
`userId` int(11) unsigned NOT NULL,
`text` text NOT NULL,
PRIMARY KEY (`id`),
KEY `conversationId` (`conversationId`),
KEY `userId` (`userId`),
CONSTRAINT `reply_ibfk_2` FOREIGN KEY (`userId`) REFERENCES `user` (`id`),
CONSTRAINT `reply_ibfk_1` FOREIGN KEY (`conversationId`) REFERENCES `conversation` (`id`)
);
CREATE TABLE `user` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(255) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
);
我在这里遇到了困难,找不到一个能满足我所有需求的解决方案。也许这里有人可以给我一些关于如何进行数据库设计的建议。
用户活动
我仍然会制作一个连接表,但要在元组上添加索引。
USER_CONV
---------------
id_user
id_conversation
active_flag
begin_flag - flag indicating if id_first_reply == id_first_visible_reply
id_first_reply - first reply in the conversation
id_first_visible_reply - first visible for a given user
id_last_reply - last visible for a given user, user if active_flag = false, otherwise NULL
index (id_user, id_conversation, active_flag)
USER_REPLY
---------------
id_user
id_conversaion
id_reply
index (id_user, id_conversation, id_reply)
USER_CONV应该可以减少创建收件箱的痛苦。不过,您还需要使用USER_REPLY,然后使用REPLY来加入它。
收件箱
如果您想让它变得快速,那么创建一种CACHED_INBOX,并创建一个具有MOST_RERECENT_USER_ACTIVITY的表。CACHED_INBOX表将包含所有收件箱数据,您不需要进行任何联接来获取相关数据,但您只需要对MOST_REQUENT_USER_ACTIVITY中的数据进行UNION。MOST_RERECENT_USER_ACTIVITY应该很小(工作速度很快),CACHED_INBOX将是"静态"的。然后每天一次,或者在服务器不太可能使用的时候每隔几天进行一次批处理CACHED_INBOX更新。除非你的用户决定永远保持对话,否则它会起作用。
优化
当然,您需要使用explain来了解查询优化器是如何使用索引的(如果它使用索引的话)。我想你需要一个像ACTIVE_CONVERSATIONS这样的表,它不会有任何索引,这会影响性能。但是,您需要对USER_CONV进行一些批量更新,这将具有更广泛的索引。你必须尝试一下,看看你对用户行为的期望是什么,这应该是架构驱动因素之一。
注意:关于索引,有一个很好的网站专门介绍索引使用的主题-e-index-luke.com