PostgreSQL 大规模随机数据生成完整指南

PostgreSQL 大规模随机数据生成完整指南


一、核心工具:generate_series + random 函数家族

PostgreSQL 自带强大的数据生成能力,无需额外工具就能生成上亿行测试数据。

基础随机函数清单

函数 返回值 示例
random() 0~1 之间的 double 0.7234561
random() * (max - min) + min 任意范围 double random() * 100
(random() * 100)::INT 整数 73
floor(random() * N)::INT 0 ~ N-1 整数 floor(random()*10)::INT
gen_random_uuid() UUID(PG 13+) 7c3a...
md5(random()::text) 32 位 MD5 字符串 'a3f9...'
now() - (random() * interval '365 days') 随机日期 一年内随机时间
chr(65 + (random()*25)::int) 随机字符 A~Z 'M'

二、generate_series:行数生成器

基础用法

sql 复制代码
-- 生成 1~10 的整数
SELECT * FROM generate_series(1, 10);

-- 生成 1~100 步长 5
SELECT * FROM generate_series(1, 100, 5);

-- 生成日期序列
SELECT generate_series(
    '2026-01-01'::date,
    '2026-12-31'::date,
    '1 day'::interval
);

-- 生成时间戳序列
SELECT generate_series(
    '2026-01-01 00:00:00'::timestamp,
    '2026-01-02 00:00:00'::timestamp,
    '1 hour'::interval
);

三、典型场景:生成各种测试数据

场景 1:生成 100 万行用户数据

sql 复制代码
-- 创建表
CREATE TABLE users (
    id          BIGSERIAL PRIMARY KEY,
    username    VARCHAR(50),
    email       VARCHAR(100),
    age         INT,
    gender      CHAR(1),
    city        VARCHAR(20),
    salary      NUMERIC(10,2),
    register_at TIMESTAMP,
    is_active   BOOLEAN
);

-- 一次性生成 100 万行(耗时约 10 秒)
INSERT INTO users (username, email, age, gender, city, salary, register_at, is_active)
SELECT
    'user_' || i                                    AS username,
    'user_' || i || '@example.com'                  AS email,
    18 + (random() * 50)::INT                       AS age,
    CASE WHEN random() < 0.5 THEN 'M' ELSE 'F' END  AS gender,
    (ARRAY['北京','上海','广州','深圳','杭州','成都','武汉','西安'])
        [1 + (random() * 7)::INT]                   AS city,
    (3000 + random() * 47000)::NUMERIC(10,2)        AS salary,
    NOW() - (random() * interval '1095 days')       AS register_at,
    random() < 0.8                                  AS is_active
FROM generate_series(1, 1000000) i;

场景 2:生成订单数据(含外键关联)

sql 复制代码
CREATE TABLE orders (
    id          BIGSERIAL PRIMARY KEY,
    user_id     BIGINT,
    order_no    VARCHAR(20),
    amount      NUMERIC(10,2),
    status      VARCHAR(20),
    created_at  TIMESTAMP
);

-- 生成 500 万订单,关联到已有的 100 万用户
INSERT INTO orders (user_id, order_no, amount, status, created_at)
SELECT
    1 + (random() * 999999)::BIGINT                              AS user_id,
    'ORD' || lpad(i::text, 12, '0')                              AS order_no,
    (random() * 9999 + 1)::NUMERIC(10,2)                         AS amount,
    (ARRAY['pending','paid','shipped','delivered','cancelled'])
        [1 + (random() * 4)::INT]                                AS status,
    NOW() - (random() * interval '365 days')                     AS created_at
FROM generate_series(1, 5000000) i;

场景 3:生成中文姓名(仿真数据)

sql 复制代码
-- 创建一个临时数据池
WITH first_names AS (
    SELECT unnest(ARRAY['张','王','李','赵','刘','陈','杨','黄',
                        '周','吴','徐','孙','胡','朱','高','林']) AS name
),
last_names AS (
    SELECT unnest(ARRAY['伟','芳','娜','秀英','敏','静','丽','强',
                        '磊','军','洋','勇','艳','杰','娟','涛']) AS name
)
INSERT INTO users (username)
SELECT
    (SELECT name FROM first_names ORDER BY random() LIMIT 1) ||
    (SELECT name FROM last_names  ORDER BY random() LIMIT 1)
FROM generate_series(1, 100000);

⚠️ 这种写法每次子查询都要排序,百万级以上慢。下面有更快方案。

改进版(百万级也快)
sql 复制代码
INSERT INTO users (username)
SELECT
    (ARRAY['张','王','李','赵','刘','陈','杨','黄',
           '周','吴','徐','孙','胡','朱','高','林'])
        [1 + (random() * 15)::INT] ||
    (ARRAY['伟','芳','娜','秀英','敏','静','丽','强',
           '磊','军','洋','勇','艳','杰','娟','涛'])
        [1 + (random() * 15)::INT]
FROM generate_series(1, 1000000);

场景 4:生成 IP 地址

sql 复制代码
SELECT
    floor(random()*256)::int || '.' ||
    floor(random()*256)::int || '.' ||
    floor(random()*256)::int || '.' ||
    floor(random()*256)::int AS ip_address
FROM generate_series(1, 100);

-- 生成 inet 类型
SELECT (
    floor(random()*256)::int || '.' ||
    floor(random()*256)::int || '.' ||
    floor(random()*256)::int || '.' ||
    floor(random()*256)::int
)::inet;

场景 5:生成 JSON 数据

sql 复制代码
CREATE TABLE events (
    id SERIAL PRIMARY KEY,
    payload JSONB,
    created_at TIMESTAMP
);

INSERT INTO events (payload, created_at)
SELECT
    jsonb_build_object(
        'event_type',  (ARRAY['click','view','purchase','share'])[1+(random()*3)::INT],
        'user_id',     (random() * 1000000)::BIGINT,
        'page',        '/page/' || (random()*100)::INT,
        'duration_ms', (random() * 10000)::INT,
        'tags',        ARRAY[
            (ARRAY['hot','new','sale','featured'])[1+(random()*3)::INT],
            (ARRAY['mobile','web','app'])[1+(random()*2)::INT]
        ]
    ) AS payload,
    NOW() - (random() * interval '30 days') AS created_at
FROM generate_series(1, 1000000);

场景 6:生成关联表(一对多)

sql 复制代码
-- 1 个用户 5~20 个订单
CREATE TABLE orders2 (
    id BIGSERIAL PRIMARY KEY,
    user_id BIGINT,
    amount NUMERIC(10,2)
);

INSERT INTO orders2 (user_id, amount)
SELECT
    u.id,
    (random() * 1000)::NUMERIC(10,2)
FROM users u
CROSS JOIN LATERAL generate_series(1, 5 + (random() * 15)::INT);

场景 7:生成正态分布数据(更真实)

random() 是均匀分布,但真实业务数据通常是正态分布。

sql 复制代码
-- Box-Muller 变换生成正态分布
CREATE OR REPLACE FUNCTION normal_random(mean NUMERIC, stddev NUMERIC)
RETURNS NUMERIC AS $$
SELECT mean + stddev *
       sqrt(-2 * ln(random())) * cos(2 * pi() * random());
$$ LANGUAGE SQL;

-- 生成均值 5000、标准差 1500 的工资
SELECT normal_random(5000, 1500)::NUMERIC(10,2)
FROM generate_series(1, 1000);

四、性能优化技巧(亿级数据生成)

优化 1:批量插入 + 关闭日志

sql 复制代码
-- 1. 设置会话参数(仅本会话生效)
SET synchronous_commit TO OFF;       -- 关闭同步提交
SET maintenance_work_mem TO '2GB';   -- 增大维护内存
SET wal_level TO minimal;            -- 需要重启,谨慎使用

-- 2. 用 UNLOGGED 表(绕过 WAL,速度提升 3-5 倍)
CREATE UNLOGGED TABLE temp_users (
    id BIGSERIAL PRIMARY KEY,
    name VARCHAR(50)
);

-- 3. 删除索引,生成完再重建
DROP INDEX IF EXISTS idx_users_email;
-- 插入数据 ...
CREATE INDEX idx_users_email ON users(email);

优化 2:分批插入(避免事务过大)

sql 复制代码
-- 一次性插 1 亿可能爆 WAL,分批插入
DO $$
DECLARE
    batch_size INT := 1000000;       -- 每批 100 万
    total      INT := 100;            -- 总共 100 批 = 1 亿
BEGIN
    FOR i IN 1..total LOOP
        INSERT INTO users (username, age)
        SELECT 'user_' || ((i-1)*batch_size + g),
               18 + (random() * 50)::INT
        FROM generate_series(1, batch_size) g;

        COMMIT;
        RAISE NOTICE '完成第 % 批,共 % 行', i, i * batch_size;
    END LOOP;
END $$;

优化 3:使用 COPY 替代 INSERT(最快)

sql 复制代码
-- 先生成 CSV 文件
\copy (
    SELECT
        i AS id,
        'user_' || i AS username,
        18 + (random() * 50)::INT AS age
    FROM generate_series(1, 10000000) i
) TO '/tmp/users.csv' WITH CSV;

-- 再用 COPY 导入(速度比 INSERT 快 10 倍以上)
\copy users(id, username, age) FROM '/tmp/users.csv' WITH CSV;

优化 4:并行生成(最快)

利用多个会话并行插入到不同的分区:

bash 复制代码
# 4 个会话并行
for i in {0..3}; do
    psql -c "INSERT INTO users SELECT ... FROM generate_series($((i*2500000+1)), $(((i+1)*2500000)))" &
done
wait

五、性能对比实测

生成 1000 万行简单用户数据的耗时:

方法 耗时 备注
普通 INSERT + generate_series ~120 秒 基准
UNLOGGED 表 + INSERT ~40 秒 3 倍提速
删除索引 + INSERT + 重建 ~70 秒 索引很多时显著
COPY FROM CSV ~25 秒 5 倍提速
4 会话并行 INSERT ~35 秒 受限 IO
COPY FROM PROGRAM + 并行 ~10 秒 最快

六、第三方工具推荐

1. pgbench(PG 自带)

经典基准测试工具,自带 TPC-B 数据生成。

bash 复制代码
# 创建并初始化测试数据
pgbench -i -s 100 mydb       # scale=100 → 1000万行 pgbench_accounts

# scale 含义
# scale=1     → 10 万行
# scale=100   → 1000 万行
# scale=1000  → 1 亿行

# 自定义脚本压测
pgbench -c 50 -j 4 -T 60 -f my_script.sql mydb

2. Faker(Python 第三方)

python 复制代码
# 安装: pip install faker psycopg2-binary
from faker import Faker
import psycopg2

fake = Faker('zh_CN')
conn = psycopg2.connect("dbname=test user=postgres")
cur = conn.cursor()

# 生成真实感的中文姓名、地址、电话等
data = [
    (fake.name(), fake.email(), fake.address(),
     fake.phone_number(), fake.date_of_birth())
    for _ in range(100000)
]

# 批量插入
cur.executemany(
    "INSERT INTO users(name, email, address, phone, birthday) VALUES (%s,%s,%s,%s,%s)",
    data
)
conn.commit()

3. pgloader(结构化导入)

bash 复制代码
# 从 MySQL/SQLite/CSV 等导入
pgloader csv://data.csv pgsql://user@host/db

4. mockaroo + COPY

mockaroo.com 在线生成各种逼真数据,下载 CSV 后用 COPY 导入。


七、实战完整案例:电商系统测试数据

sql 复制代码
-- ============== 1. 创建表结构 ==============
CREATE UNLOGGED TABLE customers (
    id BIGSERIAL PRIMARY KEY,
    name VARCHAR(50),
    email VARCHAR(100),
    phone VARCHAR(20),
    register_at TIMESTAMP
);

CREATE UNLOGGED TABLE products (
    id BIGSERIAL PRIMARY KEY,
    name VARCHAR(100),
    category VARCHAR(50),
    price NUMERIC(10,2),
    stock INT
);

CREATE UNLOGGED TABLE orders (
    id BIGSERIAL PRIMARY KEY,
    customer_id BIGINT,
    total NUMERIC(10,2),
    status VARCHAR(20),
    created_at TIMESTAMP
);

CREATE UNLOGGED TABLE order_items (
    id BIGSERIAL PRIMARY KEY,
    order_id BIGINT,
    product_id BIGINT,
    quantity INT,
    price NUMERIC(10,2)
);

-- ============== 2. 生成 100 万客户 ==============
INSERT INTO customers (name, email, phone, register_at)
SELECT
    (ARRAY['张','王','李','赵','刘'])[1+(random()*4)::INT] ||
    (ARRAY['伟','芳','娜','磊','静'])[1+(random()*4)::INT],
    'cust_' || i || '@test.com',
    '13' || lpad((random() * 999999999)::BIGINT::text, 9, '0'),
    NOW() - (random() * interval '1095 days')
FROM generate_series(1, 1000000) i;

-- ============== 3. 生成 1 万商品 ==============
INSERT INTO products (name, category, price, stock)
SELECT
    '商品_' || i,
    (ARRAY['服饰','电子','食品','家居','美妆','图书','运动','母婴'])
        [1+(random()*7)::INT],
    (random() * 9999 + 10)::NUMERIC(10,2),
    (random() * 1000)::INT
FROM generate_series(1, 10000) i;

-- ============== 4. 生成 1000 万订单 ==============
INSERT INTO orders (customer_id, total, status, created_at)
SELECT
    1 + (random() * 999999)::BIGINT,
    (random() * 5000)::NUMERIC(10,2),
    (ARRAY['pending','paid','shipped','delivered','cancelled'])
        [1+(random()*4)::INT],
    NOW() - (random() * interval '365 days')
FROM generate_series(1, 10000000);

-- ============== 5. 生成订单明细(每订单 1-5 件)==============
INSERT INTO order_items (order_id, product_id, quantity, price)
SELECT
    o.id,
    1 + (random() * 9999)::BIGINT,
    1 + (random() * 4)::INT,
    (random() * 1000)::NUMERIC(10,2)
FROM orders o
CROSS JOIN LATERAL generate_series(1, 1 + (random() * 4)::INT);

-- ============== 6. 转回 LOGGED 并建索引 ==============
ALTER TABLE customers SET LOGGED;
ALTER TABLE products SET LOGGED;
ALTER TABLE orders SET LOGGED;
ALTER TABLE order_items SET LOGGED;

CREATE INDEX idx_customers_email ON customers(email);
CREATE INDEX idx_orders_customer ON orders(customer_id);
CREATE INDEX idx_orders_created  ON orders(created_at);
CREATE INDEX idx_oi_order        ON order_items(order_id);
CREATE INDEX idx_oi_product      ON order_items(product_id);

-- ============== 7. 收集统计信息 ==============
ANALYZE customers;
ANALYZE products;
ANALYZE orders;
ANALYZE order_items;

八、常用代码片段速查

sql 复制代码
-- 随机整数 1~100
(random() * 99 + 1)::INT
floor(random() * 100)::INT + 1

-- 随机小数(保留 2 位)
(random() * 100)::NUMERIC(10,2)

-- 随机布尔(50% 概率)
random() < 0.5

-- 随机布尔(80% 概率为 true)
random() < 0.8

-- 随机字符串(长度 10)
substr(md5(random()::text), 1, 10)

-- 随机 UUID (PG 13+)
gen_random_uuid()

-- 随机日期(过去 1 年)
NOW() - (random() * interval '365 days')

-- 随机日期(未来 30 天)
NOW() + (random() * interval '30 days')

-- 数组随机取一个
(ARRAY['A','B','C','D'])[1 + (random() * 3)::INT]

-- 加权随机(70% A, 20% B, 10% C)
CASE
    WHEN random() < 0.7 THEN 'A'
    WHEN random() < 0.9 THEN 'B'
    ELSE 'C'
END

-- NULL 概率(10% 为 NULL)
CASE WHEN random() < 0.1 THEN NULL ELSE 'value' END

-- 随机金额(指数分布,模拟真实消费)
(-100 * ln(1 - random()))::NUMERIC(10,2)

-- 随机手机号(中国)
'1' || (ARRAY['3','5','7','8','9'])[1 + (random() * 4)::INT] ||
lpad((random() * 999999999)::BIGINT::text, 9, '0')

-- 随机邮箱
substr(md5(random()::text), 1, 8) || '@' ||
(ARRAY['gmail.com','qq.com','163.com','outlook.com'])[1+(random()*3)::INT]

-- 随机 IPv4
(floor(random()*256)::int || '.' ||
 floor(random()*256)::int || '.' ||
 floor(random()*256)::int || '.' ||
 floor(random()*256)::int)::inet

九、生成数据的注意事项

1. 控制随机种子(可重复测试)

sql 复制代码
SELECT setseed(0.42);            -- 固定种子,后续 random() 结果可重复
SELECT random() FROM generate_series(1, 5);

2. 大数据量生成时的内存问题

sql 复制代码
-- ❌ 一次性生成 1 亿行可能 OOM
INSERT INTO t SELECT ... FROM generate_series(1, 100000000);

-- ✅ 分批
DO $$ ... LOOP 中分批生成 ... END $$;

3. 索引建议

生成数据前删索引,生成完再建

  • 千万级数据时,不删索引插入会慢 3-10 倍
  • 主键索引可以保留(用 SERIAL 自增)

4. 序列重置

sql 复制代码
-- 如果用 BIGSERIAL,生成数据后需要重置序列
SELECT setval('users_id_seq', (SELECT MAX(id) FROM users));

5. 避免触发器影响

sql 复制代码
-- 临时禁用触发器
ALTER TABLE users DISABLE TRIGGER ALL;
-- 插入数据 ...
ALTER TABLE users ENABLE TRIGGER ALL;

十、生产用途警告

场景 是否推荐
性能测试 / 压测 ✅ 强烈推荐
开发环境测试 ✅ 推荐
学习 SQL / 调优 ✅ 推荐
演示 Demo ✅ 推荐
生产环境填充 绝对不要!

⚠️ 永远不要用这种方式直接在生产环境插入随机数据,会污染真实数据。


一句话总结

PostgreSQL 自带的 generate_series + random() 是大规模测试数据生成的"瑞士军刀",配合 UNLOGGED 表 + 删索引 + 分批插入 + COPY 可以在分钟级生成亿级数据。

核心套路:

sql 复制代码
INSERT INTO target_table (col1, col2, ...)
SELECT
    <random 表达式>,
    <random 表达式>,
    ...
FROM generate_series(1, N);

性能要求高时改用 COPY ,需要更逼真的中文/真实场景数据时配合 Python Fakermockaroo

相关推荐
techdashen1 小时前
Cloudflare + PlanetScale:在边缘运行全栈应用,数据库也不例外
数据库
敖正炀1 小时前
PostgreSQL 安全机制:认证、加密与审计
postgresql
飞飞传输1 小时前
数字化科研提速关键 构建安全可控一体化跨网数据传输体系
大数据·运维·安全
m0_624578591 小时前
如何在phpMyAdmin中导入GZIP压缩格式文件_加速传输并突破文件大小限制
jvm·数据库·python
敖正炀1 小时前
PostgreSQL 性能调优:内存、I/O 与连接管理
postgresql
m0_495496411 小时前
mysql数据库表名区分大小写吗_通过lower case table names配置
jvm·数据库·python
Elastic 中国社区官方博客1 小时前
Elastic 的 AI agent skills
大数据·人工智能·elasticsearch·搜索引擎·ai·全文检索
瀚高PG实验室1 小时前
PG的JDBC对SQL中绑定变量个数的限制
数据库·sql·postgresql·瀚高数据库
敖正炀1 小时前
PostgreSQL 分区表与逻辑复制实战
postgresql