Django ORM 进行‘不存在就插入,存在就跳过‘用get_or_create/if not .exists/try: except IntegrityError 的性能差异

用户表有Mobile字段,设置了unique=True情况下,可以利用唯一性冲突来实现'不存在就插入,存在就跳过'。但那种方式性能更佳呢?

因为mysql有INSERT IGNORE INTO 专门应对这种场景,用原生SQL效率肯定最高。而Django ORM项目中使用原生SQL有点麻烦。

Django ORM可选的方式有三个,那种开销最小呢:

get_or_create -- 缺点:如果数据存在则加载,开销大

try: except IntegrityError  --缺点:处理冲突也需要额外开销

if not .exists --需要先select,也是有额外开销

chatgpt一开始推荐get_or_create+only, gemini一开始推荐try: except IntegrityError,经过一轮质疑之后两位AI都倾向于objects.bulk_create([new], ignore_conflicts=True)。 为了验证,拿了1500条数据进行验证,发现.bulk_create([new], ignore_conflicts=True)在批量处理时最高效。但逐条处理并不占优势,但有点意外的是if not .exists 效率并不比try: except IntegrityError低,两者用时非常接近,多轮测试各有胜负。

结论:推荐if not .exists,既直观,效率又不低。

from django.core.management.base import BaseCommand
from django.db import IntegrityError
from your_app.models import AccDetail  # 替换为你的 app 名称和模型路径

import time

class Command(BaseCommand):
    help = "在现有表上,用自定义手机号列表测试四种“已存在”场景下的插入/忽略性能"

    def handle(self, *args, **options):
        # —— 在这里填入你的、已确保存在于表中的手机号列表 —— #
        PHONES = [
            '13800000000',
            '13900000000',
            '13600000000',
            '13100000000'

        ]

        results = {}

        # 方案一:exists() + save()
        start = time.time()
        for phone in PHONES:
            if not AccDetail.objects.filter(Mobile=phone).exists():
                AccDetail(Mobile=phone).save()
        results['exists_then_save'] = time.time() - start

        # 方案二:create() + 捕获 IntegrityError
        start = time.time()
        for phone in PHONES:
            try:
                AccDetail.objects.create(Mobile=phone)
            except IntegrityError:
                pass
        results['create_with_exception'] = time.time() - start

        # 方案三:bulk_create(ignore_conflicts=True)
        start = time.time()
        objs = [AccDetail(Mobile=phone) for phone in PHONES]
        AccDetail.objects.bulk_create(objs, ignore_conflicts=True)
        results['bulk_create_ignore'] = time.time() - start

        # 方案四:bulk_create(ignore_conflicts=True)
        start = time.time()
        for phone in PHONES:
            AccDetail.objects.bulk_create([AccDetail(Mobile=phone)], ignore_conflicts=True)
        results['bulk_create_ignore_one_by_one'] = time.time() - start

        # 方案五:only('id','Mobile').get_or_create()
        start = time.time()
        for phone in PHONES:
            AccDetail.objects.only('id', 'Mobile').get_or_create(Mobile=phone)
        results['get_or_create_only'] = time.time() - start

        # 方案六:无only.get_or_create()
        start = time.time()
        for phone in PHONES:
            AccDetail.objects.get_or_create(Mobile=phone)
        results['get_or_create_no_only'] = time.time() - start

        # 输出对比结果
        self.stdout.write("\n方案                            耗时 (秒)")
        self.stdout.write("-" * 60)
        for name, elapsed in results.items():
            self.stdout.write(f"{name:30s}{elapsed:8.4f}")

结果:

方案                            耗时 (秒)
------------------------------------------------------------
exists_then_save                0.4612
create_with_exception           0.4909
bulk_create_ignore              0.2935
bulk_create_ignore_one_by_one   0.8326
get_or_create_only              1.3603
get_or_create_no_only          24.4371




方案                            耗时 (秒)
------------------------------------------------------------
exists_then_save                0.4629
create_with_exception           0.4745
bulk_create_ignore              0.2848
bulk_create_ignore_one_by_one   0.7976
get_or_create_only              1.0892
get_or_create_no_only          22.4974

 exists_then_save 和 get_or_create_only 都有select操作,为什么效率差异这么明显呢? 

# exists() 最终语句
SELECT (1) AS "__exists" FROM `iqdii_openacc` WHERE `Mobile` = '13812340000' LIMIT 1;


# only(...).get_or_create() 最终语句
SELECT `id`,`Mobile`  FROM `iqdii_openacc` WHERE `Mobile` = '13812340000' LIMIT 1;

因为get_or_create()要返回模型实例,拿到sql返回的数据后 Django 会把这一行数据实例化成一个 AccDetail 对象:分配内存、解析两个字段值、调用 __init__、关联 _state 等元数据。这些 Python 对象创建和属性赋值的成本,往往比一次轻量的 exists() 查询要高。

exists()只返回一个布尔值,无模型实例化

备注:Django对于插入一条新记录,.create() 和 .save() 方法是等效的,create只是对.save进行了封装,本质都是.save()

脚本需要放在 Django 目录的这个文件夹下 (需要新建management和commands,且都要有__init__.py,__init__.py内容可以为空)


your_project/
└── yourapp/               # 这里的 yourapp 要在 INSTALLED_APPS 里
    ├── __init__.py
    ├── models.py
    ├── views.py
    ├── management/        # 必须叫 management
    │   ├── __init__.py
    │   └── commands/      # 必须叫 commands
    │       ├── __init__.py
    │       └── bench_conflict_existing.py  # 你的脚本
    └── ..






执行命令:
python manage.py bench_conflict_existing

用这种方法就不需要借助页面,不需要设置urls.py 了,

也不需要django.setup()。

import django
django.setup()

class Command 是 Django 约定俗成的管理命令(management command)入口

当你执行 manage.py bench_conflict_existing 时,Django 会先加载项目的 settings、初始化 ORM、缓存、日志系统等;然后才实例化你的 Command 并调用 handle()。

如果你写成普通脚本,就要自己写 django.setup()、手动处理 sys.path,代码可用性和可维护性都打折。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值