我与 Rick James 就此进行了相当长时间的讨论,我们提出了使用复合键来替换 int 限制接近 20 亿的自动增量 pk 的想法。我的表将在几个月内轻松达到这个限制,因为我们每月都会捕获接近几亿的数据。下面是我的桌子的样子。关键表是gdata
所以我使用 3 个字段来合成主表PRIMARY KEY (alarmTypeID,vehicleID,gDateTime)
。然后我有另一个表称为警报表。两者之间的联系是一对多的。这意味着其中的一个数据gdata
可以有零个或多个alarms
与之相关。它们之间的联系是vehicleID
和gDateTime
。
CREATE TABLE `gdata` (
`alarmTypeID` tinyint(4) NOT NULL DEFAULT '0',
`fleetID` smallint(11) NOT NULL,
`fleetGroupID` smallint(11) DEFAULT NULL,
`fleetSubGroupID` smallint(11) DEFAULT NULL,
`deviceID` mediumint(11) NOT NULL,
`vehicleID` mediumint(11) NOT NULL,
`gDateTime` datetime NOT NULL,
`insertDateTime` datetime NOT NULL,
`latitude` float NOT NULL,
`longitude` float NOT NULL,
`speed` smallint(11) NOT NULL
-- (see full text)
) ;
ALTER TABLE `gdata`
ADD PRIMARY KEY (`alarmTypeID`,`vehicleID`,`gDateTime`),
ADD KEY `gDateTime` (`gDateTime`),
ADD KEY `fleetID` (`fleetID`,`vehicleID`,`gDateTime`);
COMMIT;
这是报警表
CREATE TABLE `alarm` (
`alarmTypeID` tinyint(4) NOT NULL,
`vehicleID` mediumint(9) NOT NULL,
`gDateTime` datetime NOT NULL,
`insertDateTime` datetime NOT NULL,
`alarmValue` varchar(5) NOT NULL,
`readWeb` enum('n','y') NOT NULL DEFAULT 'n',
`readWebDateTime` datetime NOT NULL,
`readMobile` enum('n','y') NOT NULL DEFAULT 'n',
`readMobileDateTim` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
ALTER TABLE `alarm`
ADD PRIMARY KEY (`alarmTypeID`,`vehicleID`,`gDateTime`);
COMMIT;
一切看起来都不错,但最近我在谷歌上搜索了一些相关主题,发现一些讨论https://www.quora.com/Is-it-a-bad-idea-to-have-a-primary-key-on- 3-or-more-columns反对复合主键,并且更愿意使用自动增量主要用于插入目的。有人可以对此进行更多说明以维护主键的复合键或返回自动增量吗?
复合键没有任何问题。但是,您必须考虑如何
InnoDB
存储数据。引用上面的链接文档:
也就是说,InnoDB 会根据你的
PRIMARY KEY
. 如果您插入的数据的 PK 增加,则不会出现页面碎片。总是会发生这种情况AUTO_INCREMENT
。如果您按时间顺序插入数据(即gDateTime
始终单调递增),请将构成 PK 的列的顺序更改为:... 将具有相同的优势,即不必“在其他行中间放置一个新行”(这意味着 B-tree 不会因每个插入而碎片化)。
但是:如果您从其他(相关)表中引用此表,则必须在引用表中始终存储 PK (
gDateTime
,alarmTypeID
,vehicleID
)。这意味着您每次节省 7 或 8 个字节的存储空间。复合 PK 将使用 2 + 1 + 8 = 11 个字节的信息(可能由于对齐而使用 12 个字节);而INT UNSIGNED AUTO_INCREMENT
, 您将在引用表中仅使用 4 个字节。您的 PK 限制为 2^32 个不同的值。如果您需要超过 2^32 的值,您将需要BIGINT AUTO_INCREMENT
,这会给您 2^64 (而且我还没有找到一个实际案例,这还不够大)。这是否有意义,很大程度上取决于您的特定情况。
joanolo 有一些好的观点,有些观点我不同意......
DATETIME
并且TIMESTAMP
没有小数秒,每个占用 5 个字节。(所以有问题的 PK 总共是 9 个字节。)DATETIME
(orTIMESTAMP
)是危险的——如果两个条目同时发生怎么办?(这个问题取决于应用程序;例如,测量卡车的位置不需要在一秒钟内读取两个 gps 读数。)PRIMARY
UNIQUE
INT
,下一个选择AUTO_INCREMENT
是 8-byteBIGINT
;与 9 个字节没有太大区别。MEDIUMINT
是 3 个字节 (vehicleID
)。具体...
我说 PK 为 0 字节,因为它包含在其余列中。辅助键的数字是辅助键列的大小 + 额外的 PK 列。(当然,索引中有很大的开销,因此这些数字不能用于计算 BTree 的最终大小。您可能需要 3 倍的软糖因子。)
一个
SELECT
与alarmTypeID
( ,vehicleID
,gDateTime
) 比 (gDateTime
,alarmTypeID
, )处理得更好vehicleID
。如果这是一个常见的查询,我认为它胜过避免碎片化的愿望。PRIMARY KEY(
alarmTypeID
,vehicleID
,gDateTime
) 避免了辅助键和数据之间的反弹。PRIMARY KEY(
gDateTime
,alarmTypeID
,vehicleID
) 不能使用警报器或车辆,并且必须越过警报器和不感兴趣的车辆。或者使用辅助键,导致来回弹跳。无论哪种情况,都慢得多。(经验法则:当数据未缓存时,旋转磁盘的速度要慢 10 倍。)