数据库表只存时间戳,带主键的表更大

同样的两张表,一张单timestamp列,另一张主键列+timestamp列,哪张占用空间更大?

2026-05-11 16:42

2 分钟 阅读

昨天在群里看到群友在讨论对于只存日志的表,主键的必要性,以及索引在滥用情况下的空间占用可能比数据还要大等等问题。大佬们对数据库的理解之透彻,甚至于讨论到了“如果我是秦始皇,那数据瞎几把存不建任何索引的查询性能是最高的”。

但其中有群友指出 {timestamp(带索引)} 的表会比 {id, timestamp}(复合主键) 占用空间更小,因为前者会创建两个b+树,后者只会创建一个,这个话题让我很感兴趣。一个一列表,一个两列表,两列的比一列的小,为什么会有这样反直觉的情况?

与其上网搜,不如自己先实验一下,我用三种主流的数据库,MySQL(同时测试myisam和innodb引擎), PostGreSQL, SQLite,按上述的方式进行测试,再看占用空间。

下方是我使用的建表语句

MySQL(myisam引擎)

DROP TABLE IF EXISTS t2_ts_myisam;
DROP TABLE IF EXISTS t1_pk_myisam;

CREATE TABLE t1_pk_myisam (
  id           BIGINT       NOT NULL,
  `timestamp`  DATETIME(6)  NOT NULL,
  PRIMARY KEY (id, `timestamp`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;

CREATE TABLE t2_ts_myisam (
  `timestamp`  DATETIME(6)  NOT NULL,
  KEY idx_t2_ts (`timestamp`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;

MySQL(innodb引擎)

DROP TABLE IF EXISTS t2_ts_innodb;
DROP TABLE IF EXISTS t1_pk_innodb;

CREATE TABLE t1_pk_innodb (
  id           BIGINT       NOT NULL,
  `timestamp`  DATETIME(6)  NOT NULL,
  PRIMARY KEY (id, `timestamp`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE t2_ts_innodb (
  `timestamp`  DATETIME(6)  NOT NULL,
  KEY idx_t2_ts (`timestamp`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

PostGreSQL

DROP TABLE IF EXISTS t2_ts;
DROP TABLE IF EXISTS t1_pk;

CREATE TABLE t1_pk (
  id          BIGINT     NOT NULL,
  "timestamp" TIMESTAMP  NOT NULL,
  PRIMARY KEY (id, "timestamp")
);

CREATE TABLE t2_ts (
  "timestamp" TIMESTAMP  NOT NULL
);
CREATE INDEX idx_t2_ts ON t2_ts ("timestamp");

SQLite

DROP TABLE IF EXISTS t2_ts;
DROP TABLE IF EXISTS t1_pk;

CREATE TABLE t1_pk (
  id          INTEGER   NOT NULL,
  "timestamp" DATETIME  NOT NULL,
  PRIMARY KEY (id, "timestamp")
);

CREATE TABLE t2_ts (
  "timestamp" DATETIME  NOT NULL
);
CREATE INDEX idx_t2_ts ON t2_ts ("timestamp");

建表后,无数据,各表大小:

数据库表名数据大小索引大小总大小总大小(字节)
MySQL MyISAMt1_pk_myisam0 bytes1024 bytes1024 bytes1024
MySQL MyISAMt2_ts_myisam0 bytes1024 bytes1024 bytes1024
MySQL InnoDBt1_pk_innodb16 kB0 bytes16 kB16384
MySQL InnoDBt2_ts_innodb16 kB16 kB32 kB32768
PostgreSQLt1_pk0 bytes8192 bytes8192 bytes8192
PostgreSQLt2_ts0 bytes8192 bytes8192 bytes8192
SQLitet1_pk4096 bytes4096 bytes8192 bytes8192
SQLitet2_ts4096 bytes4096 bytes8192 bytes8192

使用python脚本,为三张表插入完全相同的随机数据,每张表 5 万条,效果如下图

tsid1

再查询各表大小:

数据库表名数据大小索引大小总大小总大小(字节)
MySQL InnoDBt1_pk_innodb2576 kB0 bytes2576 kB2637824
MySQL InnoDBt2_ts_innodb2576 kB1552 kB4128 kB4227072
MySQL MyISAMt1_pk_myisam830 kB1132 kB1962 kB2009168
MySQL MyISAMt2_ts_myisam439 kB826 kB1265 kB1295824
PostgreSQLt1_pk2168 kB1552 kB3752 kB3842048
PostgreSQLt2_ts1776 kB1296 kB3104 kB3178496
SQLitet1_pk1824 kB2116 kB3940 kB4034560
SQLitet2_ts1660 kB1892 kB3552 kB3637248

可以看到 myisam, pg, sqlite 都呈现出 id + ts 组合主键占用的空间大于 ts 加索引,只有 innodb 相反

再加一百万行数据试试

数据库表名数据大小索引大小总大小总大小(字节)
MySQL InnoDBt1_pk_innodb36 MB0 bytes36 MB37322752
MySQL InnoDBt2_ts_innodb33 MB36 MB68 MB71450624
MySQL MyISAMt1_pk_myisam16 MB22 MB38 MB40152640
MySQL MyISAMt2_ts_myisam8789 kB16 MB25 MB25984064
PostgreSQLt1_pk42 MB30 MB72 MB75849728
PostgreSQLt2_ts35 MB30 MB64 MB67231744
SQLitet1_pk37 MB43 MB79 MB83304448
SQLitet2_ts33 MB37 MB70 MB73342976

差异更明显了,为什么会这样?

(我暂时也不知道,后续补全)