- 文档
- 教程
- 知识库
【免费下载链接】til
:memo: Today I Learned
PostgreSQL 内置了三种尺寸的整数类型——smallint(int2)、integer(int4)与bigint(int8),它们分别占用 2、4、8 个字节,且全部为有符号整数。本文以本仓库的 integers-in-postgres.md 为主线,系统梳理三种整数类型的存储大小、精确取值范围、字面量的自动类型推断规则,并结合仓库中 postgres-does-not-support-unsigned-integers.md、checking-the-type-of-a-value.md 等关联笔记,深入讲解pg_typeof调试技巧、无符号整数缺失的替代方案以及建表与迁移中的最佳实践。读完你将能准确选择整数列类型、解释pg_typeof的推断结果,并规避整数溢出与类型变更陷阱。
三种整数类型:同族同源,尺寸不同
PostgreSQL 的整数家族没有"种类"之分,只有"尺寸"之别。官方名称分别是:
| 类型名 | 别名 | 占用字节 | 位宽 | 取值范围 |
|---|---|---|---|---|
smallint | int2 | 2 字节 | 16 位 | -32768 ~ +32767 |
integer | int4 | 4 字节 | 32 位 | -2147483648 ~ +2147483647 |
bigint | int8 | 8 字节 | 64 位 | -9223372036854775808 ~ +9223372036854775807 |
三者都只能表示有符号整数。以 4 字节的integer为例,32 位二进制能表示的取值范围是 -2147483648 到 +2147483647,即全部 4 字节所能容纳的数值;同理,2 字节的smallint覆盖 -32768 到 +32767,8 字节的bigint覆盖 -9223372036854775808 到 +9223372036854775807。
值得注意的是,别名int2、int4、int8并不仅是文档里的简称,它们在系统目录pg_type中就是真实存在的类型名。仓库笔记 types-by-category.md 演示了如何通过数值类型的类别代码N枚举全部数值类型:
select typname from pg_type where typcategory = 'N'; typname ----------------- int8 int2 int4 regproc oid float4 float8 money numeric ... (17 rows)可以看到int8、int2、int4正是pg_type目录中的正式类型名,smallint、integer、bigint只是面向用户的 SQL 别名。
用 pg_typeof 观察字面量的自动推断
正如 integers-in-postgres.md 所强调的:虽然列可以被显式限制为特定尺寸的整数,但 PostgreSQL 在"即席"处理整数(即 SQL 字面量、表达式中间值)时足够聪明,默认使用integer,仅在必要时才升级为bigint。
这一点可以用pg_typeof()函数直接验证。pg_typeof()返回任意表达式在 PostgreSQL 中的实际数据类型,是排查类型推断问题最直接的工具(详见仓库笔记 checking-the-type-of-a-value.md):
> select pg_typeof(55); pg_typeof ----------- integer > select pg_typeof(99999999999999999); pg_typeof ----------- bigint字面量55落在integer的范围内,于是被推断为integer(int4);而99999999999999999(约 10^17)明显超出 32 位有符号整数上限 2147483647,PostgreSQL 自动将其推断为bigint(int8)。这正是"默认integer,必要时才bigint"这一策略的直观体现——字面量的推断完全由数值大小驱动。
pg_typeof还能揭示一些细微行为。例如对一个字符串字面量调用它,PostgreSQL 暂时无法判定该用text还是varchar,会返回unknown:
> select pg_typeof('hello'); pg_typeof ----------- unknown此时只需显式标注目标类型即可得到确定结果:
> select pg_typeof('hello'::varchar); pg_typeof ------------------- character varying同理,如果你想强制一个超出integer范围的数值在计算中被当作smallint(尽管会溢出),或用显式类型让字面量按预期宽度参与运算,都可以借助::int2、::int4、::int8这类类型转换语法。
没有无符号整数:范围限制与替代方案
由于三种整数类型全部是有符号的,PostgreSQL 并不提供 MySQL 那样的UNSIGNED修饰符。仓库笔记 postgres-does-not-support-unsigned-integers.md 专门讨论了这一点:
以
integer为例,我们只能存储 -2147483648 到 +2147483647 之间的数值——这是 4 字节所能容纳的全部。若系统支持 4 字节无符号整数,理论上可以从 0 一直表示到 4294967295。
这意味着如果你原本指望"数据类型本身"来强制某列数据非负(比如商品数量、访问计数),在 PostgreSQL 中必须另想办法。该笔记给出了两个常见方案:
- CHECK 约束:直接为列添加非负校验,例如
create table inventory ( quantity integer check (quantity > 0) ); - 自定义 DOMAIN 类型:创建一个限制合法取值的用户定义域类型,例如
create domain non_negative_int as integer check (value >= 0);不过该笔记作者的评价是:"使用域类型的交互方式有些笨拙,不值得为此投入精力。"
需要特别提醒的是,无论 CHECK 约束还是 DOMAIN 类型,都只是近似模拟无符号整数——它们并没有真正扩大取值范围。也就是说,即使加上check (value >= 0),integer列的最大值依然是 2147483647,而不是无符号版的 4294967295。如果你的业务数据可能超过有符号上限,正确做法是直接选用更大的整数类型(如bigint),而不是靠约束"挤"出高位。
建表实践:主键与常用整数列
理解三种整数类型的字节与范围后,建表时的选择就清晰了。仓库笔记 create-a-composite-primary-key.md 展示了整数作为外键与主键组成部分的典型用法:
create table plane_tickets ( passenger_id integer references passengers not null, flight_id integer references flights not null, confirmation_number varchar(6) not null, seat_assignment varchar not null, primary key (passenger_id, flight_id) );这里的外键列都使用了默认的integer——对于绝大多数业务主键(数据量在 21 亿以内)它完全够用,而且 4 字节比bigint的 8 字节在存储和索引上更省空间。
至于自增主键,仓库笔记 generate-modern-primary-key-columns.md 给出了一条重要建议:serial/bigserial已经过时,PostgreSQL 官方 wiki 明确 "Don't use serial",新应用应优先使用 identity 列。它对比了两种写法:
传统serial写法(不推荐):
create table books ( id serial primary key, title text not null, author text not null, created_at timestamptz not null default now(), updated_at timestamptz not null default now() );现代 identity 列写法(推荐):
create table books ( id int primary key generated always as identity, title text not null, author text not null, created_at timestamptz not null default now(), updated_at timestamptz not null default now() );如果预计行数会超过 21 亿,则应把主键列换成bigint(或bigint generated always as identity)。
类型迁移:从 int 到 bigint 何时需要 USING
当业务规模增长、integer不再够用时,常见操作是把列升级为bigint。仓库笔记 switch-non-castable-column-type-with-using-clause.md 指出:从int到bigint属于可以自动类型转换(cast)的变更,PostgreSQL 知道如何原地转换现有数据,因此可以直接执行:
alter table users alter column id set data type bigint;之所以不需要using子句,是因为整数类型之间的加宽转换(int2→int4→int8)是无损且内建的,PostgreSQL 能自动完成。与之对比,int到uuid这类不存在内建转换路径的变更,就必须显式提供using子句告诉数据库如何生成新值:
alter table users alter column id set data type uuid using (gen_random_uuid());因此,当你规划"把id从int扩成bigint"这样的迁移时,可以直接使用上面的alter column ... set data type语句,PostgreSQL 会负责把既有数据的类型安全地加宽。
小结与选型建议
围绕本仓库 integers-in-postgres.md 的核心内容,可以总结出以下要点:
- PostgreSQL 只有三种尺寸的整数:
smallint(2 字节,±32767)、integer(4 字节,±2147483647)、bigint(8 字节,±9223372036854775807),别名分别为int2、int4、int8; - 三者均为有符号整数,PostgreSQL不支持无符号整数,非负约束需用 CHECK 约束或 DOMAIN 类型近似实现,且无法扩大取值范围;
- 在即席表达式(字面量)中,PostgreSQL 默认推断为
integer,超出其范围才升级为bigint,可用pg_typeof()验证; - 建表选型时,常规主键与计数用
integer即可;高容量场景选用bigint;新应用主键优先使用generated always as identity而非serial; int→bigint属于自动可转换的类型变更,可直接通过alter column ... set data type完成,无需using子句。
掌握了这些字节与范围知识,你就能在设计表结构时做出精准取舍,也能在面对pg_typeof的输出、溢出告警或类型迁移需求时快速定位问题。
- 文档
- 教程
- 知识库
【免费下载链接】til
:memo: Today I Learned
相关推荐
Maka WorkHub Coordination Session:不用第二套数据库,如何实现持久协调层
Maka WorkHub Coordination Session:不用第二套数据库,如何实现持久协调层 在 Maka 仓库中,WorkHub(工作中枢)需要一
人工智能AI Agent自主智能体工具调用交互助手AI 评测TypeScript 中从值推导类型:类型推断、字面量收窄与 typeof 提取实践
TypeScript 中从值推导类型:类型推断、字面量收窄与 typeof 提取实践 导读 type from value (Type à partir d'u
文档教程Ruff ty 类型系统深度解析:Integer `Literal` 整数字面量类型的语义与实战
Ruff ty 类型系统深度解析:Integer Literal 整数字面量类型的语义与实战 整数字面量类型(Integer Literal Type)是静态类
开发工具Lint格式化静态分析CLI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考