PostgreSQL: PostgreSQL数据类型完整指南

最后更新:2026-08-26

选择正确的数据类型是数据库设计的基础——它决定了存储效率、查询性能和数据精度。

1. 你将学到


2. 一个开发者的真实故事

(1) 痛点:数据类型选择困惑

Bob 在设计电商数据库的表结构时,遇到一系列选择困难:

(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) 收益


3. 数据类型选择决策图

100%
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)反而提升查询性能,因为类型操作符可以走索引。

📖 小节


📝 作业

  1. 基础题(难度⭐):创建一个 countries 表,包含:id(SERIAL 主键)、name(VARCHAR(100))、iso_code(CHAR(2))、population(BIGINT)、gdp DECIMAL(15,2)、is_developed(BOOLEAN)。插入 3 条测试数据。

  2. 进阶题(难度⭐⭐):创建一个 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 网段的日志。

  3. 挑战题(难度⭐⭐⭐):设计一个 financial_transactions 表,需要处理多币种(USD/EUR/JPY/CNY),金额精度不同(USD/EUR 2 位小数,JPY 0 位,CNY 2 位)。用 DECIMAL + CHECK 约束实现:不同币种有不同的小数位数要求。写出建表 SQL 和 3 条不同币种的插入示例。

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏