PostgreSQL: PostgreSQL数据类型完整指南
最后更新:2026-08-26
选择正确的数据类型是数据库设计的基础——它决定了存储效率、查询性能和数据精度。
1. 你将学到
- 整数类型(SMALLINT / INTEGER / BIGINT)
- 自增序列(SERIAL / BIGSERIAL / IDENTITY)
- 浮点与定点(REAL / DOUBLE / DECIMAL / NUMERIC)
- 字符串类型(CHAR / VARCHAR / TEXT)
- 日期时间类型(DATE / TIME / TIMESTAMP / TIMESTAMPTZ / INTERVAL)
- 特殊类型(UUID / INET / BOOLEAN / ENUM / JSONB)
2. 一个开发者的真实故事
(1) 痛点:数据类型选择困惑
Bob 在设计电商数据库的表结构时,遇到一系列选择困难:
- 用户 ID 用 INTEGER 还是 BIGINT?万一用户量超过 21 亿怎么办?
- 商品价格用 REAL 还是 DECIMAL?听说浮点有精度问题?
- 商品描述用 VARCHAR(5000) 还是 TEXT?
- 时间戳用 TIMESTAMP 还是 TIMESTAMPTZ?全球用户怎么办?
- IP 地址用 VARCHAR 存还是有什么专用类型?
(2) 精确定型的解法
PostgreSQL 提供了丰富的专用数据类型,精确选择既能节省存储又能保证精度:
SQL
-- Correct type choices for an e-commerce database
CREATE TABLE smart_products (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, -- future-proof ID
name VARCHAR(200) NOT NULL, -- bounded string
description TEXT, -- unbounded text
price DECIMAL(10, 2) NOT NULL, -- exact money
weight_kg REAL, -- approximate is OK
is_available BOOLEAN DEFAULT true, -- yes/no flag
created_at TIMESTAMPTZ DEFAULT NOW(), -- timezone-aware
source_ip INET, -- IP address type
attributes JSONB DEFAULT '{}'::jsonb -- flexible schema
);
(3) 收益
- DECIMAL 保证金额零精度损失(89.99 永远是 89.99,不会变成 89.9899999)
- TIMESTAMPTZ 自动处理时区,全球用户看到各自本地时间
- INET 支持 IP 范围查询,比 VARCHAR 存 IP 高效 10 倍
- JSONB 灵活存储动态属性,无需频繁 ALTER TABLE
3. 数据类型选择决策图
graph TB
START[What kind of data?] --> NUM{Numeric?}
NUM -->|Yes| INT{Need exact<br/>precision?}
INT -->|Yes, money/rates| DEC[DECIMAL / NUMERIC]
INT -->|No, measurements| FLOAT[REAL / DOUBLE PRECISION]
NUM -->|Whole numbers| RANGE{Value range?}
RANGE -->|-32768 to 32767| SMALL[SMALLINT]
RANGE -->|-2.1B to 2.1B| INT2[INTEGER]
RANGE -->|Larger| BIG[BIGINT]
NUM -->|Auto-increment ID| AUTO[SERIAL / IDENTITY]
START --> STR{Text?}
STR -->|Fixed length| CHAR[CHAR]
STR -->|Bounded variable| VAR[VARCHAR n]
STR -->|Unbounded| TEXT2[TEXT]
START --> TIME{Date/Time?}
TIME -->|Date only| DATE2[DATE]
TIME -->|Time only| TIME2[TIME]
TIME -->|Timestamp| TZ{Timezone?}
TZ -->|Yes, global app| TSTZ[TIMESTAMPTZ]
TZ -->|No, local only| TS[TIMESTAMP]
TIME -->|Duration| IV[INTERVAL]
START --> SPEC{Special?}
SPEC -->|Yes/No| BOOL[BOOLEAN]
SPEC -->|UUID| UUID2[UUID]
SPEC -->|IP address| IP[INET / CIDR]
SPEC -->|Enum list| ENUM2[ENUM]
SPEC -->|JSON doc| JSON[JSONB]
4. 整数类型
| 类型 | 存储 | 范围 | 典型用途 |
|---|---|---|---|
SMALLINT |
2 字节 | -32,768 ~ 32,767 | 年龄、评分、状态码 |
INTEGER (INT) |
4 字节 | -2,147,483,648 ~ 2,147,483,647 | 常用整数、主键 ID |
BIGINT |
8 字节 | ±9,223,372,036,854,775,807 | 大表 ID、金额(分) |
▶ 示例:整数类型选择
SQL
-- SMALLINT for small-range values
CREATE TABLE ratings (
user_id INTEGER REFERENCES users(id),
product_id INTEGER REFERENCES products(id),
score SMALLINT CHECK (score BETWEEN 1 AND 5), -- 1-5 is well within SMALLINT range
PRIMARY KEY (user_id, product_id)
);
-- INTEGER for most IDs
CREATE TABLE categories (
id SERIAL PRIMARY KEY, -- SERIAL = INTEGER + auto-increment sequence
name VARCHAR(100) NOT NULL
);
-- BIGINT for high-growth tables
CREATE TABLE audit_log (
id BIGSERIAL PRIMARY KEY, -- BIGSERIAL = BIGINT + auto-increment
action VARCHAR(50),
details JSONB,
created_at TIMESTAMPTZ DEFAULT NOW()
);
输出:
TEXT
📖 仅展示
CREATE TABLE
💡 提示: 不确定用 INTEGER 还是 BIGINT?对于主键 ID,如果预计行数超过 10 亿,直接用 BIGINT。多花 4 字节/行(每百万行仅多 4 MB),但避免后期 ALTER TABLE 重写大表。
5. 自增序列
(1) SERIAL vs IDENTITY
| 方式 | 语法 | 标准 | 能否手动插入 | 推荐 |
|---|---|---|---|---|
SERIAL |
id SERIAL PRIMARY KEY |
PG 专有 | 可以 | 旧项目兼容 |
BIGSERIAL |
id BIGSERIAL PRIMARY KEY |
PG 专有 | 可以 | 旧项目兼容 |
IDENTITY |
id INT GENERATED ALWAYS AS IDENTITY |
SQL 标准 | 需 OVERRIDING | ✅ 新项目 |
▶ 示例:IDENTITY 列(推荐)
SQL
-- SQL-standard identity column (PostgreSQL 10+)
CREATE TABLE orders_v2 (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
user_id INTEGER REFERENCES users(id),
total DECIMAL(12, 2) DEFAULT 0
);
-- Insert without specifying id (auto-generated)
INSERT INTO orders_v2 (user_id, total) VALUES (1, 99.99);
-- Attempt to manually insert id (will FAIL with GENERATED ALWAYS)
-- INSERT INTO orders_v2 (id, user_id, total) VALUES (100, 1, 50.00);
-- Use GENERATED BY DEFAULT if you need manual override sometimes
CREATE TABLE logs (
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
message TEXT
);
输出:
TEXT
📖 仅展示
INSERT 0 1
6. 浮点与定点
(1) REAL / DOUBLE vs DECIMAL
| 类型 | 存储 | 精度 | 适用场景 |
|---|---|---|---|
REAL |
4 字节 | 6 位有效数字 | 科学计算、传感器 |
DOUBLE PRECISION |
8 字节 | 15 位有效数字 | 科学计算、统计 |
DECIMAL(p,s) |
可变 | 精确 | 金额、费率(推荐) |
NUMERIC(p,s) |
可变 | 精确 | 同 DECIMAL |
▶ 示例:浮点精度问题
SQL
-- Float precision trap: 0.1 + 0.2 != 0.3
SELECT 0.1::REAL + 0.2::REAL = 0.3::REAL AS float_equal;
-- Output: f (false!)
-- DECIMAL has no precision loss
SELECT 0.1::DECIMAL + 0.2::DECIMAL = 0.3::DECIMAL AS decimal_equal;
-- Output: t (true!)
输出:
TEXT
📖 仅展示
id | name | value
----+----------+-------
1 | example | 42
(1 row)
▶ 示例:金额用 DECIMAL
SQL
-- DECIMAL(precision, scale)
-- precision = total digits, scale = digits after decimal point
-- DECIMAL(10,2) = up to 99,999,999.99
CREATE TABLE products_precise (
id SERIAL PRIMARY KEY,
name VARCHAR(200),
price DECIMAL(10, 2) NOT NULL, -- max 99,999,999.99
tax_rate DECIMAL(5, 4) DEFAULT 0.0875, -- 0.0875 = 8.75%
discount NUMERIC(5, 2) DEFAULT 0.00 -- max 999.99%
);
-- Money calculations are exact
SELECT name,
price,
price * tax_rate AS tax_amount,
price + (price * tax_rate) AS price_with_tax
FROM products_precise;
输出:
TEXT
📖 仅展示
count
-------
5
(1 row)
| 精度选择 | DECIMAL 参数 | 最大值 | 适用 |
|---|---|---|---|
| 小型电商 | DECIMAL(8,2) |
999,999.99 | 日均交易 < 1M |
| 大型电商 | DECIMAL(12,2) |
99,999,999,999.99 | 全球电商 |
| 加密货币 | DECIMAL(20,8) |
极大值 | BTC 精度 8 位 |
7. 字符串类型
| 类型 | 存储 | 最大长度 | 适用 |
|---|---|---|---|
CHAR(n) |
定长,补空格 | n | 哈希值、ISO 代码 |
VARCHAR(n) |
变长 | n | 有长度限制的文本 |
TEXT |
变长 | 无限制 | 无长度限制的文本 |
▶ 示例:字符串类型对比
SQL
-- CHAR: fixed-length (padded with spaces)
SELECT LENGTH('abc'::CHAR(5));
-- Output: 5 (padded to 5 chars)
-- VARCHAR: variable-length with limit
SELECT LENGTH('abc'::VARCHAR(5));
-- Output: 3 (stored as-is, max 5)
-- TEXT: variable-length, no limit
SELECT LENGTH('abc'::TEXT);
-- Output: 3 (no limit)
-- Performance test: VARCHAR vs TEXT are identical in PG
-- (Unlike MySQL where VARCHAR is faster than TEXT)
输出:
TEXT
📖 仅展示
id | name | value
----+----------+-------
1 | example | 42
(1 row)
💡 提示: PostgreSQL 中 VARCHAR 和 TEXT 的查询/存储性能完全相同。PG 不会因为 TEXT 没有长度限制就变慢。选择依据仅是:是否需要数据库层强制长度限制。
8. 日期时间类型
| 类型 | 存储 | 范围 | 适用 |
|---|---|---|---|
DATE |
4 字节 | 4713 BC ~ 5874897 AD | 仅日期(生日、节假日) |
TIME |
8 字节 | 00:00:00 ~ 24:00:00 | 仅时间(营业时间) |
TIMESTAMP |
8 字节 | 4713 BC ~ 294276 AD | 日期+时间(无时区) |
TIMESTAMPTZ |
8 字节 | 同上 | 日期+时间+时区(✅ 推荐) |
INTERVAL |
16 字节 | ±178000000 年 | 时间间隔 |
▶ 示例:TIMESTAMP vs TIMESTAMPTZ
SQL
-- Set timezone to UTC
SET timezone = 'UTC';
-- Insert the same moment in different types
INSERT INTO test_times (ts_no_tz, ts_with_tz) VALUES
('2026-07-13 10:00:00', '2026-07-13 10:00:00+00');
-- Change to Tokyo timezone
SET timezone = 'Asia/Tokyo';
-- TIMESTAMP (no timezone) shows the same literal
SELECT ts_no_tz FROM test_times;
-- Output: 2026-07-13 10:00:00 (unchanged, could be any timezone!)
-- TIMESTAMPTZ converts to local timezone
SELECT ts_with_tz FROM test_times;
-- Output: 2026-07-13 19:00:00+09 (10:00 UTC = 19:00 Tokyo)
输出:
TEXT
📖 仅展示
INSERT 0 1
▶ 示例:日期时间函数
SQL
-- Current date and time
SELECT NOW(); -- 2026-07-13 10:30:00.123456+00
SELECT CURRENT_DATE; -- 2026-07-13
SELECT CURRENT_TIMESTAMP; -- same as NOW()
-- Date arithmetic with INTERVAL
SELECT NOW() + INTERVAL '7 days'; -- one week from now
SELECT NOW() - INTERVAL '3 months'; -- 3 months ago
-- Age calculation
SELECT AGE(TIMESTAMP '1990-05-15'); -- 36 years 1 month 28 days
-- Extract parts
SELECT EXTRACT(YEAR FROM NOW()); -- 2026
SELECT EXTRACT(MONTH FROM NOW()); -- 7
SELECT EXTRACT(DOW FROM NOW()); -- 1 (Monday, 0=Sunday)
输出:
TEXT
📖 仅展示
id | name | value
----+----------+-------
1 | example | 42
(1 row)
9. 特殊类型
(1) UUID
SQL
-- Enable uuid-ossp extension
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- Generate different UUID versions
SELECT uuid_generate_v4(); -- random UUID (most common)
SELECT uuid_generate_v1(); -- time-based UUID
| UUID 版本 | 生成方式 | 适用场景 |
|---|---|---|
| v1 | 时间戳 + MAC 地址 | 需要时间排序 |
| v4 | 随机 | 大多数场景(推荐) |
| v7 | 时间戳 + 随机(新标准) | 时间排序 + 随机 |
(2) INET / CIDR(网络地址)
▶ 示例:IP 地址存储与查询
SQL
-- INET: single IP or IP range
CREATE TABLE access_log (
id BIGSERIAL PRIMARY KEY,
source_ip INET NOT NULL,
access_time TIMESTAMPTZ DEFAULT NOW()
);
INSERT INTO access_log (source_ip) VALUES
('192.168.1.100'),
('10.0.0.5'),
('2001:db8::1');
-- Query: find all IPs in a subnet (impossible with VARCHAR!)
SELECT source_ip FROM access_log
WHERE source_ip << '192.168.1.0/24'::INET;
-- << means "is contained within"
-- CIDR: network range
SELECT '192.168.1.0/24'::CIDR;
输出:
TEXT
📖 仅展示
INSERT 0 1
| 操作符 | 含义 | 示例 |
|---|---|---|
<< |
包含于 | 192.168.1.5' << '192.168.1.0/24' = true |
>> |
包含 | '192.168.1.0/24' >> '192.168.1.5' = true |
= |
等于 | '192.168.1.5'::INET = '192.168.1.5'::INET |
(3) BOOLEAN
▶ 示例:布尔值的三种表示
SQL
-- Boolean accepts multiple representations
SELECT true, 't', 'true', 'yes', 'on', '1'; -- all = TRUE
SELECT false, 'f', 'false', 'no', 'off', '0'; -- all = FALSE
-- Boolean in WHERE clause
SELECT name FROM products WHERE is_available IS TRUE;
SELECT name FROM products WHERE is_available IS NOT FALSE;
输出:
TEXT
📖 仅展示
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(4) ENUM
▶ 示例:自定义枚举类型
SQL
-- Create an enum type (once, reusable across tables)
CREATE TYPE order_status AS ENUM (
'pending', 'paid', 'shipped', 'delivered', 'cancelled'
);
-- Use in table definition
CREATE TABLE orders_enum (
id SERIAL PRIMARY KEY,
status order_status DEFAULT 'pending'
);
-- Enum values are validated automatically
INSERT INTO orders_enum (status) VALUES ('pending'); -- OK
-- INSERT INTO orders_enum (status) VALUES ('unknown'); -- ERROR!
输出:
TEXT
📖 仅展示
INSERT 0 1
⚠️ 注意: PG 的 ENUM 类型一旦创建,增加新值需要
ALTER TYPE ... ADD VALUE(PG 9.1+ 可在事务中执行)。如果值经常变化,用 VARCHAR + CHECK 约束更灵活。
10. 数据类型对比总表
| 需求 | ❌ 不推荐 | ✅ 推荐 | 原因 |
|---|---|---|---|
| 金额 | REAL / FLOAT | DECIMAL(p,s) | 浮点有精度损失 |
| 时间戳 | TIMESTAMP | TIMESTAMPTZ | 全球应用需时区 |
| IP 地址 | VARCHAR | INET | INET 支持范围查询 |
| 是/否 | INTEGER (0/1) | BOOLEAN | 语义清晰 |
| 长文本 | VARCHAR(9999) | TEXT | TEXT 无长度限制且性能相同 |
| 状态枚举 | VARCHAR + CHECK | ENUM 或 VARCHAR+CHECK | 枚举值稳定用 ENUM,常变用 CHECK |
| 灵活属性 | 多列 NULL | JSONB | 一列存动态字段 |
| 自增 ID | SERIAL | IDENTITY | SQL 标准语法 |
11. 完整示例:多类型综合表
SQL
-- ============================================
-- Complete example: table with all major types
-- A sensor data collection system
-- ============================================
-- Create enum type
CREATE TYPE sensor_status AS ENUM ('active', 'inactive', 'maintenance');
-- Create table with diverse data types
CREATE TABLE sensors (
-- Identity
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
uuid UUID DEFAULT uuid_generate_v4() UNIQUE,
-- Text
name VARCHAR(100) NOT NULL,
description TEXT,
location_code CHAR(10),
-- Numeric
latitude DECIMAL(9, 6) CHECK (latitude BETWEEN -90 AND 90),
longitude DECIMAL(9, 6) CHECK (longitude BETWEEN -180 AND 180),
altitude REAL,
-- Network
ip_address INET,
subnet CIDR,
-- Status
status sensor_status DEFAULT 'active',
is_online BOOLEAN DEFAULT false,
-- Time
installed_at DATE,
last_reading_at TIMESTAMPTZ,
reading_interval INTERVAL DEFAULT INTERVAL '5 minutes',
-- Flexible data
metadata JSONB DEFAULT '{}'::jsonb,
-- Audit
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Insert sample data
INSERT INTO sensors (name, latitude, longitude, ip_address, status, installed_at, metadata)
VALUES (
'Temperature Sensor A1',
35.6762, 139.6503,
'192.168.1.50',
'active',
'2025-01-15',
'{"model": "TX-200", "unit": "celsius", "range_min": -40, "range_max": 85}'::jsonb
);
-- Query with type-specific operators
SELECT name, ip_address, metadata->>'model' AS model
FROM sensors
WHERE ip_address << '192.168.1.0/24'::INET
AND status = 'active';
❓ 常见问题
Q DECIMAL 和 NUMERIC 有什么区别?
A 在 PostgreSQL 中,DECIMAL 和 NUMERIC 完全等价,可以互换使用。SQL 标准中两者有微妙区别,但 PG 实现上完全相同。推荐用 DECIMAL(更直观)。
Q SERIAL 列的序列断号了怎么办?
A SERIAL 使用序列对象,INSERT 失败或回滚后序列值不会回退(这是设计如此,保证并发安全)。断号是正常现象,不影响功能。如果必须连续,请在应用层处理而非依赖序列。
Q VARCHAR(50) 改为 VARCHAR(100) 需要锁表吗?
A PostgreSQL 中增大 VARCHAR 长度不锁表(不重写数据),是瞬间完成的操作。减小长度或改类型才会锁表。
Q JSON 和 JSONB 该选哪个?
A 几乎总是选 JSONB。JSONB 是二进制存储,支持索引查询,速度快。JSON 是文本存储,每次查询需要重新解析。唯一用 JSON 的场景:需要保留输入的空格/键顺序(如日志审计)。
Q 为什么推荐 TIMESTAMPTZ 而不是 TIMESTAMP?
A TIMESTAMPTZ 在存储时转为 UTC,读取时转为客户端时区。这意味着全球用户看到各自本地时间,不会混乱。TIMESTAMP 存什么读什么,跨时区应用会产生歧义。
Q 一个表里混用多种数据类型会影响性能吗?
A 不会。PG 按列存储类型信息,查询时只读取需要的列。合理使用专用类型(如 INET 存 IP)反而提升查询性能,因为类型操作符可以走索引。
📖 小节
- 整数选择:SMALLINT(小范围)/ INTEGER(默认)/ BIGINT(大表 ID),优先用 IDENTITY 自增
- 金额必须用 DECIMAL,浮点类型有精度损失
- 字符串:VARCHAR(有长度限制) / TEXT(无限制),性能相同
- 时间:推荐 TIMESTAMPTZ(带时区),INTERVAL 用于时间计算
- 特殊类型:UUID(分布式 ID)/ INET(IP 地址)/ BOOLEAN(是/否)/ ENUM(固定枚举)/ JSONB(灵活属性)
- 核心原则:选最精确的类型,宁大勿小(BIGINT 多 4 字节/行,但 ALTER TABLE 重写大表代价更大)
📝 作业
-
基础题(难度⭐):创建一个
countries表,包含:id(SERIAL 主键)、name(VARCHAR(100))、iso_code(CHAR(2))、population(BIGINT)、gdp DECIMAL(15,2)、is_developed(BOOLEAN)。插入 3 条测试数据。 -
进阶题(难度⭐⭐):创建一个
server_logs表,包含:id(BIGINT IDENTITY 主键)、source_ip(INET)、request_time(TIMESTAMPTZ)、response_time_ms(INTEGER)、is_error(BOOLEAN)、metadata(JSONB)。插入 2 条日志记录,然后查询所有来自10.0.0.0/8网段的日志。 -
挑战题(难度⭐⭐⭐):设计一个
financial_transactions表,需要处理多币种(USD/EUR/JPY/CNY),金额精度不同(USD/EUR 2 位小数,JPY 0 位,CNY 2 位)。用 DECIMAL + CHECK 约束实现:不同币种有不同的小数位数要求。写出建表 SQL 和 3 条不同币种的插入示例。