Skip to content

PostgreSQL UUID 函数详解

概述

UUID(Universally Unique Identifier,通用唯一标识符)是一个 128 位的数字,用于在分布式系统中唯一标识信息。PostgreSQL 提供了内置的 UUID 生成和处理函数,这些函数在现代应用程序中广泛应用于主键生成、会话标识、文档编号等场景。

INFO

UUID 的优势在于不需要中央协调就能保证全局唯一性,特别适合分布式系统和微服务架构。

UUID 生成函数

gen_random_uuid()

语法:

sql
gen_random_uuid() → uuid

功能说明: 此函数返回版本 4(随机)UUID,这是最常用的 UUID 类型,适用于大多数应用程序。版本 4 UUID 基于随机数生成,具有高度的随机性和唯一性。

实际应用示例

示例 1:用户表主键生成

sql
-- 创建用户表,使用 UUID 作为主键
CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    username VARCHAR(50) NOT NULL,
    email VARCHAR(100) NOT NULL,
    created_at TIMESTAMP DEFAULT NOW()
);

-- 插入数据时自动生成 UUID
INSERT INTO users (username, email)
VALUES ('alice', '[email protected]');

-- 查看生成的结果
SELECT * FROM users;

输出示例:

id                                   | username | email               | created_at
-------------------------------------|----------|-------------------- |-------------------------
a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11 | alice    | [email protected]  | 2024-01-15 10:30:45.123

分析过程:

  • DEFAULT gen_random_uuid() 确保每次插入新记录时自动生成唯一的 UUID
  • UUID 格式为 8-4-4-4-12 的十六进制字符串,总长度为 36 个字符
  • 使用 UUID 作为主键可以避免在分布式环境中的 ID 冲突问题

示例 2:订单号生成系统

sql
-- 创建订单表
CREATE TABLE orders (
    order_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    customer_id INTEGER NOT NULL,
    order_number VARCHAR(20) GENERATED ALWAYS AS ('ORD-' || UPPER(SUBSTRING(order_id::TEXT, 1, 8))) STORED,
    total_amount DECIMAL(10,2),
    status VARCHAR(20) DEFAULT 'pending'
);

-- 插入订单数据
INSERT INTO orders (customer_id, total_amount)
VALUES (1001, 299.99), (1002, 150.50);

-- 查看生成的订单号
SELECT order_id, order_number, customer_id, total_amount, status FROM orders;

输出示例:

order_id                             | order_number | customer_id | total_amount | status
-------------------------------------|--------------|-------------|--------------|--------
b1e2f3a4-5c6d-7e8f-9a0b-1c2d3e4f5a6b | ORD-B1E2F3A4 | 1001        | 299.99       | pending
c2f3g4h5-6d7e-8f9a-0b1c-2d3e4f5g6h7i | ORD-C2F3G4H5 | 1002        | 150.50       | pending

分析过程:

  • 使用 UUID 确保订单 ID 的全局唯一性
  • 通过 GENERATED ALWAYS AS 自动生成人类可读的订单编号
  • 即使在多个数据库实例间同步数据,也不会出现 ID 冲突

UUID 扩展模块

uuid-ossp 模块

PostgreSQL 的 uuid-ossp 模块提供了额外的函数,实现了其他标准的 UUID 生成算法。

sql
-- 启用 uuid-ossp 扩展
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

-- 使用不同版本的 UUID 生成函数
SELECT
    uuid_generate_v1() AS "版本1-基于时间",
    uuid_generate_v1mc() AS "版本1-MAC随机",
    uuid_generate_v4() AS "版本4-随机";

各版本 UUID 对比:

版本生成方式优点缺点适用场景
版本 1基于时间+MAC 地址有时序性,可排序可能泄露 MAC 地址需要时序的日志系统
版本 4完全随机隐私性好,无信息泄露无时序性通用场景,用户 ID

UUID 数据提取函数

uuid_extract_timestamp()

语法:

sql
uuid_extract_timestamp(uuid) → timestamp with time zone

功能说明: 从 UUID 版本 1 中提取带时区的时间戳。对于其他版本,此函数返回 null。

实际应用示例

sql
-- 启用 uuid-ossp 扩展以使用版本1 UUID
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

-- 创建日志表使用版本1 UUID
CREATE TABLE system_logs (
    log_id UUID PRIMARY KEY DEFAULT uuid_generate_v1(),
    log_level VARCHAR(10),
    message TEXT,
    created_at TIMESTAMP DEFAULT NOW()
);

-- 插入日志数据
INSERT INTO system_logs (log_level, message) VALUES
('INFO', '用户登录成功'),
('ERROR', '数据库连接失败'),
('WARN', '内存使用率达到80%');

-- 从 UUID 中提取时间戳并与实际创建时间对比
SELECT
    log_id,
    log_level,
    message,
    created_at AS "实际创建时间",
    uuid_extract_timestamp(log_id) AS "UUID中的时间戳"
FROM system_logs;

输出示例:

log_id                               | log_level | message           | 实际创建时间         | UUID中的时间戳
-------------------------------------|-----------|-------------------|---------------------|--------------------
01ef2a3b-4c5d-11ef-8e6f-0242ac120002 | INFO      | 用户登录成功      | 2024-01-15 10:30:45 | 2024-01-15 10:30:45+08
01ef2a3c-4c5d-11ef-8e6f-0242ac120002 | ERROR     | 数据库连接失败    | 2024-01-15 10:30:46 | 2024-01-15 10:30:46+08

分析过程:

  • 版本 1 UUID 包含时间戳信息,可以用于数据恢复和审计
  • 提取的时间戳精度可能与实际创建时间略有差异
  • 这个特性在日志系统和事件溯源中特别有用

uuid_extract_version()

语法:

sql
uuid_extract_version(uuid) → smallint

功能说明: 从符合 RFC 4122 标准的 UUID 中提取版本号。对于其他变体,此函数返回 null。

实际应用示例

sql
-- 测试不同来源的 UUID 版本
WITH uuid_samples AS (
    SELECT
        gen_random_uuid() AS random_uuid,
        uuid_generate_v1() AS time_uuid,
        uuid_generate_v4() AS random_v4_uuid
)
SELECT
    random_uuid,
    uuid_extract_version(random_uuid) AS "random_uuid版本",
    time_uuid,
    uuid_extract_version(time_uuid) AS "time_uuid版本",
    random_v4_uuid,
    uuid_extract_version(random_v4_uuid) AS "random_v4_uuid版本"
FROM uuid_samples;

输出示例:

random_uuid                          | random_uuid版本 | time_uuid                            | time_uuid版本 | random_v4_uuid                       | random_v4_uuid版本
-------------------------------------|----------------|--------------------------------------|---------------|--------------------------------------|------------------
a1b2c3d4-e5f6-4789-abcd-ef1234567890 | 4              | 01ef2a3b-4c5d-11ef-8e6f-0242ac120002 | 1             | f1e2d3c4-b5a6-4978-9876-543210fedcba | 4

分析过程:

  • gen_random_uuid() 生成版本 4 UUID
  • uuid_generate_v1() 生成版本 1 UUID
  • 版本信息可用于验证 UUID 的生成方式和数据完整性

UUID 比较操作

PostgreSQL 为 UUID 提供了标准的比较操作符,支持排序和索引操作。

比较操作示例

sql
-- 创建测试数据
CREATE TEMP TABLE uuid_test AS
SELECT gen_random_uuid() AS id, 'test_' || generate_series(1,5) AS name;

-- UUID 比较操作
SELECT
    id,
    name,
    id = (SELECT MIN(id) FROM uuid_test) AS "是否为最小UUID",
    id > (SELECT MIN(id) FROM uuid_test) AS "是否大于最小UUID"
FROM uuid_test
ORDER BY id;

UUID 支持的操作符:

操作符说明示例
=等于uuid1 = uuid2
<>不等于uuid1 <> uuid2
<小于uuid1 < uuid2
<=小于等于uuid1 <= uuid2
>大于uuid1 > uuid2
>=大于等于uuid1 >= uuid2

性能考量和最佳实践

索引性能

sql
-- UUID 作为主键的性能测试表
CREATE TABLE performance_test (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    data TEXT
);

-- 创建额外索引
CREATE INDEX idx_performance_test_id_hash ON performance_test USING hash (id);

-- 插入测试数据
INSERT INTO performance_test (data)
SELECT 'test_data_' || generate_series(1, 10000);

-- 性能分析
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM performance_test WHERE id = '01ef2a3b-4c5d-11ef-8e6f-0242ac120002';

存储空间对比

sql
-- 不同主键类型的存储空间对比
CREATE TEMP TABLE space_comparison AS
SELECT
    pg_column_size(1::INTEGER) AS "整数大小(字节)",
    pg_column_size(1::BIGINT) AS "长整数大小(字节)",
    pg_column_size(gen_random_uuid()) AS "UUID大小(字节)",
    pg_column_size('user_12345'::VARCHAR) AS "字符串大小(字节)";

SELECT * FROM space_comparison;

输出示例:

整数大小(字节) | 长整数大小(字节) | UUID大小(字节) | 字符串大小(字节)
--------------|----------------|---------------|------------------
4             | 8              | 16            | 13

最佳实践建议

TIP

使用建议

  1. 主键选择:对于分布式系统,推荐使用 UUID 作为主键
  2. 版本选择:大多数情况下使用版本 4(gen_random_uuid()
  3. 索引策略:UUID 主键建议使用 B-tree 索引(默认)
  4. 存储优化:考虑使用二进制存储格式以节省空间

WARNING

注意事项

  1. UUID 比整数主键占用更多存储空间
  2. 随机 UUID 可能影响 B-tree 索引的插入性能
  3. 在高并发插入场景下,考虑使用版本 1 UUID 以获得更好的局部性

高级应用场景

分布式主键生成

sql
-- 多节点环境下的用户表设计
CREATE TABLE distributed_users (
    user_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    node_id VARCHAR(10) NOT NULL,
    username VARCHAR(50) NOT NULL,
    created_at TIMESTAMP DEFAULT NOW(),

    -- 复合索引优化查询
    CONSTRAINT unique_username_per_node UNIQUE (node_id, username)
);

-- 插入来自不同节点的用户
INSERT INTO distributed_users (node_id, username) VALUES
('NODE_001', 'alice'),
('NODE_002', 'bob'),
('NODE_001', 'charlie');

-- 查询特定节点的用户
SELECT user_id, node_id, username
FROM distributed_users
WHERE node_id = 'NODE_001'
ORDER BY created_at;

API 令牌生成

sql
-- API 令牌管理系统
CREATE TABLE api_tokens (
    token_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id INTEGER NOT NULL,
    token_name VARCHAR(100),
    expires_at TIMESTAMP,
    is_active BOOLEAN DEFAULT true,
    created_at TIMESTAMP DEFAULT NOW(),

    -- 索引优化令牌查找
    INDEX idx_api_tokens_active (token_id) WHERE is_active = true
);

-- 生成API令牌
INSERT INTO api_tokens (user_id, token_name, expires_at)
VALUES (1001, 'mobile_app_token', NOW() + INTERVAL '30 days');

-- 令牌验证函数
CREATE OR REPLACE FUNCTION validate_token(token UUID)
RETURNS BOOLEAN AS $$
BEGIN
    RETURN EXISTS (
        SELECT 1 FROM api_tokens
        WHERE token_id = token
        AND is_active = true
        AND (expires_at IS NULL OR expires_at > NOW())
    );
END;
$$ LANGUAGE plpgsql;

-- 使用示例
SELECT validate_token('01ef2a3b-4c5d-11ef-8e6f-0242ac120002') AS "令牌是否有效";

数据迁移和转换

从整数 ID 迁移到 UUID

sql
-- 原有表结构(整数ID)
CREATE TABLE old_products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    price DECIMAL(10,2)
);

-- 新表结构(UUID)
CREATE TABLE new_products (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    legacy_id INTEGER,
    name VARCHAR(100),
    price DECIMAL(10,2)
);

-- 数据迁移脚本
INSERT INTO new_products (legacy_id, name, price)
SELECT id, name, price FROM old_products;

-- 创建映射表以支持旧系统兼容
CREATE TABLE id_mapping (
    legacy_id INTEGER PRIMARY KEY,
    new_uuid UUID NOT NULL REFERENCES new_products(id)
);

INSERT INTO id_mapping (legacy_id, new_uuid)
SELECT legacy_id, id FROM new_products;

通过这些详细的示例和说明,您可以充分理解和掌握 PostgreSQL 中 UUID 函数的使用方法,并将其有效应用到实际的数据库设计和开发中。UUID 函数为现代应用程序提供了强大的唯一标识符生成能力,特别适用于分布式系统和微服务架构。