使用Django ORM bulk_create批量插入PostgreSQL数据逐渐变慢求助
我之前也碰到过类似PostgreSQL搭配Django bulk_create()越跑越慢的问题,给你几个实际验证有效的优化方向:
你当前用的是1000条一批,但PostgreSQL对单批次的最优值并非固定——太大的批次会让数据库WAL(预写日志)压力陡增,太小又会频繁触发事务开销。建议根据单条记录的字段数量、是否包含大文本等,测试500、2000甚至5000的批次大小。我之前把批次调到2000后,插入速度稳定了不少。
Django默认每个bulk_create会开启独立事务,循环里频繁创建/提交事务会产生额外开销。可以用transaction.atomic()将多个批次包裹,或者手动控制每个批次的事务:
from django.db import transaction import time entries = [] start = time.time() for i, row in enumerate(initial_data_list): serializer = serializer_class(data=row, **kwargs) serializer.is_valid(raise_exception=True) entries.append(MyModel(**serializer.initial_data)) if i % 2000 == 0 and entries: with transaction.atomic(): MyModel.objects.bulk_create(entries) end = time.time() _logger.info('processed %d inserts... (%.2f seconds per batch)' % (i, end-start)) start = time.time() entries = [] # 清空批次列表 # 处理最后一批剩余数据 if entries: with transaction.atomic(): MyModel.objects.bulk_create(entries)
这样能减少事务的创建和提交次数,降低数据库锁竞争与日志写入的额外消耗。
如果你的表有大量索引、外键约束或唯一约束,每插入一批数据时数据库都要更新索引、检查约束,这会随着数据量增长越来越慢。如果是一次性初始化数据这类离线场景,可以先禁用约束,导入完成后再恢复:
from django.db import connection # 临时禁用触发器与索引(仅适合可信数据源的离线导入) with connection.cursor() as cursor: cursor.execute('ALTER TABLE myapp_mymodel DISABLE TRIGGER ALL;') cursor.execute('DROP INDEX IF EXISTS myapp_mymodel_some_field_idx;') # 执行你的bulk_create插入逻辑... # 恢复触发器与索引 with connection.cursor() as cursor: cursor.execute('ALTER TABLE myapp_mymodel ENABLE TRIGGER ALL;') cursor.execute('CREATE INDEX myapp_mymodel_some_field_idx ON myapp_mymodel(some_field);')
⚠️ 注意:此操作需确保导入数据完全合法(不会违反约束),且仅能用于离线场景,绝对不能在线上实时插入时使用。
你现在每条数据都走serializer.is_valid(),数据量超大时,这部分CPU开销会持续累积。如果initial_data_list来自可信来源(比如已经过预处理的内部数据),可以直接跳过Serializer验证,直接构造模型实例,能节省大量时间。如果必须验证,也可以考虑实现批量验证逻辑,减少重复校验的开销。
数据库层面的配置也会影响插入速度,建议检查以下参数:
- 调大
work_mem和maintenance_work_mem,让数据库处理批量插入时有更多内存可用,减少磁盘IO; - 调整
max_wal_size(PostgreSQL 9.5+)和wal_buffers,优化WAL写入策略,降低checkpoint的触发频率; - 若使用固态硬盘,可适当调小
wal_writer_delay,降低WAL写入延迟。
调整前建议先通过pg_stat_activity和pg_stat_bgwriter监控数据库状态,定位具体瓶颈。
bulk_create的batch_size参数(Django 2.2+) Django 2.2及以上版本的bulk_create支持batch_size参数,内部会自动将大数据集拆分为合理批次插入,省去自己写循环的麻烦,且实现更优化:
entries = [] for row in initial_data_list: serializer = serializer_class(data=row, **kwargs) serializer.is_valid(raise_exception=True) entries.append(MyModel(**serializer.initial_data)) # 自动分批插入 MyModel.objects.bulk_create(entries, batch_size=2000)
不过如果数据量达到百万级以上,还是建议分批构造实例,避免内存占用过高。
内容的提问来源于stack exchange,提问作者hannu40k

