PostgreSQL

关注公众号 jb51net

关闭
首页 > 数据库 > PostgreSQL > PostgreSQL大规模随机数据生成

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

作者:zxrhhm

本文详细介绍了使用PostgreSQL自带的generate_series和random()函数家族快速生成大规模测试数据的方法和技巧,涵盖基础用随机函数、典型应用场景、性能优化、第三方工具推荐、实战案例及注意事项,需要的朋友可以参考下

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

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

基础随机函数清单

函数返回值示例
random()0~1 之间的 double0.7234561
random() * (max - min) + min任意范围 doublerandom() * 100
(random() * 100)::INT整数73
floor(random() * N)::INT0 ~ 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:行数生成器

基础用法

-- 生成 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 万行用户数据

-- 创建表
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:生成订单数据(含外键关联)

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:生成中文姓名(仿真数据)

-- 创建一个临时数据池
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);

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

改进版(百万级也快)

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

场景 4:生成 IP 地址

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 数据

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:生成关联表(一对多)

-- 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() 是均匀分布,但真实业务数据通常是正态分布。

-- 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:批量插入 + 关闭日志

-- 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:分批插入(避免事务过大)

-- 一次性插 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(最快)

-- 先生成 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:并行生成(最快)

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

# 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 数据生成。

# 创建并初始化测试数据
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 第三方)

# 安装: 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(结构化导入)

# 从 MySQL/SQLite/CSV 等导入
pgloader csv://data.csv pgsql://user@host/db

4. mockaroo + COPY

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

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

-- ============== 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;

八、常用代码片段速查

-- 随机整数 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. 控制随机种子(可重复测试)

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

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

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

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

3. 索引建议

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

4. 序列重置

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

5. 避免触发器影响

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

十、生产用途警告

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

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

一句话总结

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

核心套路:

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

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

以上就是PostgreSQL大规模随机数据生成的完整指南的详细内容,更多关于PostgreSQL大规模随机数据生成的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文