使用一张表是正确的做法,因为它已正确规范化。添加新事件类型不需要新表。维护参照完整性并利用索引为用户检索和排序所有事件也容易得多。(如果您将它们放在单独的表中,获取用户的所有事件并按时间排序可能比使用一张表要慢得多!)
但是,有一些方法可以使这些表更小,以节省空间并保持索引小:
- 使用 an
enum()
来定义您的事件类型。如果您有少量事件,则每行最多使用一个字节。
- 使用整数类型从相同数量的字节
UNSIGNED
中获取更多EventID
和s。UserID
- 如果您不需要完整的日期范围(可能),请使用 TIMESTAMP 类型与 DATETIME 类型相比,每行节省 4 个字节。
- 如果您只使用 ipv4 地址,请将 IP 存储为无符号 4 字节整数并使用 INET_ATON() 和 INET_NTOA() 来回转换。这是最大的赢家:VARCHAR 类型至少需要 16 个字节,并且您可能会使用固定的行长度格式。
我推荐这样的表格格式:
CREATE TABLE Events (
`EventID` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
`UserID` MEDIUMINT UNSIGNED NOT NULL COMMENT 'this allows a bit more than 16 million users, and your indexes will be smaller',
`EventType` ENUM('add','delete','share') NOT NULL,
`Time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`IP` INTEGER UNSIGNED NOT NULL DEFAULT 0,
PRIMARY KEY (`EventID`),
FOREIGN KEY (`UserID`) REFERENCES `Users` (`UserId`) ON UPDATE CASCADE ON DELETE CASCADE,
KEY (UserID)
);
如果您使用 MyISAM 存储它,您的行长度将为 16 字节,使用固定格式。这意味着每百万行需要 16MB 的数据空间,而索引可能需要一半的空间(取决于您使用的索引)。这非常紧凑,mysql 可能大部分时间都可以将表的整个工作部分保存在内存中。
然后是创建最常见操作所需的索引的问题。例如,如果您始终显示某个用户在某个时间范围内的所有事件,请替换KEY (UserID)
为INDEX userbytime (UserID, Time)
. 然后类似的查询SELECT * FROM Events WHERE UserID=? AND Time BETWEEN ? AND ?
会非常快。