Django-ORM框架操作数据库不生效问题的定位示例

news2024/11/25 6:30:40

本文详细描述使用Django 的ORM框架操作PostgreSQL数据库删除不生效问题的定位过程及解决方案,并总结使用ORM框架操作数据库不生效的问题的通用定位方法

问题描述

最近使用Django 的ORM框架操作PostgreSQL数据库总是出现删除不生效(尤其是在并发的时候)。业务代码中也没有任何报错。

定位过程

  • 首先,我们怀疑是SQL语句拼装错误(比如ID不对),导致了删除不生效

通过在Python日志中打印ORM框架的SQL以及返回的操作结果,发现delete操作返回的记录数是1。且SQL中的ID符合业务逻辑,说明相应SQL语句是执行成功的。排除了这条猜测

  • 接着我们怀疑DELETE操作后,数据又被其他业务CREATE回来了

通过在数据库中增加触发器,将nfinst表的写操作记录到nfinst_audit表,发现没有删除操作。排除了这条猜测

create table nfinst_audit(
operation       char(1)   not null,
stamp           timestamp not null,
userid          text      not null,
nfinstid        text      not null,
order_id        SERIAL    ,
addr            text      not null,
port            text      not null
);
--将DELETE、UPDATE、INSERT操作记录到nfinst_audit表中
create or replace function process_nfinst_audit() returns trigger as $nfinst_audit$
begin
   if (TG_OP = 'DELETE') then
     insert into nfinst_audit(operation,stamp,userid,nfinstid,addr,port) VALUES('D',now(),user,old.nfinstid,inet_client_addr(),inet_client_port());
     return old;
   elsif (TG_OP = 'UPDATE') then
     insert into nfinst_audit(operation,stamp,userid,nfinstid,addr,port) VALUES('U',now(),user,new.nfinstid,inet_client_addr(),inet_client_port());
     return new;
   elsif (TG_OP = 'INSERT') then
     insert into nfinst_audit(operation,stamp,userid,nfinstid,addr,port) VALUES('I',now(),user,new.nfinstid,inet_client_addr(),inet_client_port());
     return new;
   end if;
   return null;
end;
$nfinst_audit$ language plpgsql;
--创建触发器
create trigger nfinst_audit 
before insert or update or delete on nfinst
for each row execute procedure process_nfinst_audit();
  • 结合以上2点,猜测是事务没有commit导致

Django默认的事务模式是autocommit,每一次数据库操作执行后都会自动提交。项目使用的SQLAlchemy库的StaticPool连接池,配合gevent使用,一个进程中的所有协程串行复用一个数据库连接。

(这里解释一下为什么要一个进程中的所有协程复用一个连接,因为Python的PostgreSQL驱动pyscopg2是由c语言编写,协程在与数据库交互时,并不会因为io操作而切走,所以即使使用多个连接,也无法带来并发能力的提升,反而会增加维护多个连接的消耗)

查看delete操作的源码,delete操作是在一个事务中执行了pre_delete signal、删除表记录、post_delete signal等操作,执行完成后自动commit或者rollback。

def delete(self):
        for model, instances in self.data.items():
            self.data[model] = sorted(instances, key=attrgetter("pk"))
        self.sort()
        deleted_counter = Counter()
        # 开启事务,语句块执行结束后会根据执行结果选择commit或者rollback
        with transaction.atomic(using=self.using, savepoint=False):
            for model, obj in self.instances_with_model():
                if not model._meta.auto_created:
                    signals.pre_delete.send(
                        sender=model, instance=obj, using=self.using
                    )
            for qs in self.fast_deletes:
                count = qs._raw_delete(using=self.using)
                deleted_counter[qs.model._meta.label] += count
            for model, instances_for_fieldvalues in six.iteritems(self.field_updates):
                query = sql.UpdateQuery(model)
                for (field, value), instances in six.iteritems(instances_for_fieldvalues):
                    query.update_batch([obj.pk for obj in instances],
                                       {field.name: value}, self.using)
            for instances in six.itervalues(self.data):
                instances.reverse()
            for model, instances in six.iteritems(self.data):
                query = sql.DeleteQuery(model)
                pk_list = [obj.pk for obj in instances]
                count = query.delete_batch(pk_list, self.using)
                deleted_counter[model._meta.label] += count
                if not model._meta.auto_created:
                    for obj in instances:
                        # 执行post_delete后置处理
                        signals.post_delete.send(
                            sender=model, instance=obj, using=self.using
                        )

这里的pre_delete signal跟post_delete signal类似于数据库的触发器,不过是在Python代码层面实现的。问题就出在这个post_delete signal上面,出错的数据表注册了post_delete signal,并在其中调用了REST接口,而调用REST接口会导致协程发生切换,如果切换后的协程也操作了数据库,会将现有的事务回滚。(因为从连接池新拿到的连接,应该保证是没有事务在执行的,如果有,就认为该连接上一次被使用时出现了异常,需回滚事务)
将post_delete相关逻辑注掉后,问题消失

解决方案

解决方法有如下几种:

  1. 直接修改Django源码,将post_delete signal的逻辑移除到事务外面(Django将post_delete的逻辑放在事务里确认有点坑,一旦post_delete出现异常就会导致事务回滚,并且事务过长也会消耗数据库资源)
  2. 修改业务代码,将delete成功后的处理逻辑由使用signal完成,改为重写Django Model的delete方法(先调用父类的delete方法,成功后再执行后置处理逻辑)
  3. 重写signal机制,post_delete使用自己实现的signal机制

最终综合考虑对业务代码的侵入性,以及后续的可维护性,我们选择了方案3来解决数据库删除不生效的问题。但在实施的时候,又发现了新的问题:django从数据库删除完数据后,会将Model对象也删除,从而导致post_delete无对象可操作。考虑到delete操作几乎不会出现rollback的情况,将post_delete移到了实际delete操作前面,类似于pre_delete。没有直接使用pre_delete是为了减少对业务代码的入侵。另外django自带的pre_delete也在事务中,而我们的改法是将signal操作移到事务外,以降低数据库压力

在models.py中做了如下修改

  1. 定义了自己的post_delete,并将业务代码中注册post_delete信号量改为从models.py导入post_delete变量
post_delete = ModelSignal(providing_args=["instance", "using"], use_caching=True)
  1. Django Model有2种方式进行删除操作,分别是直接对一条Model记录删除,以及对QuerySet进行删除。所以需要定义自己的Model类以及QuerySet基类,并让需要进行post_delete操作的Model类继承前面自定义的基类
class CModel_QuerySet(models.query.QuerySet):
    def delete(self):
        # 将post_delete信号量触发操作移到了事务外面
        for inst in self:
            post_delete.send(
                sender=self.model, instance=inst, using=None
            )
        super(CModel_QuerySet, self).delete()
class CModel_CustomManager(models.Manager):
    # custom QuerySet for snap QuerySet.update operations
    def get_queryset(self):
        return CModel_QuerySet(self.model, using=self._db)
# 自定义的Model基类
class CModelWithUpdateSignal(models.Model):
    class Meta:
        abstract = True
    # custom models.Manager for snap QuerySet.update operations
    objects = CModel_CustomManager()
    def delete(self, *args, **kwargs):
        # 将post_delete信号量触发操作移到了事务外面
        post_delete.send(
            sender=self.__class__, instance=self, using=None
        )
        super(CModelWithUpdateSignal, self).delete(*args, **kwargs)
# 需要进行post_delete操作的Model类
class NfInstModel(CModelWithUpdateSignal):
    ……

总结

ORM框架操作数据库,可以抽象至如下流程

如果出现操作数据库不生效,但是也没有报错的情况。可以从以下几个方面来排查问题 

  1. 是否SQL本身执行未生效(通常是业务逻辑导致,比如DELETE操作传错了ID),可以在ORM框架源码中加日志,将SQL执行结果打印出来
  2. 是否本次操作被其他操作覆盖,可以对数据表增加触发器,将CREATE、UPDATE、DELETE操作记录到另一张表。通过查看操作记录来确认是否是业务逻辑覆盖的问题
  3. 是否是事务没有COMMIT,可以在ORM框架源码中COMMIT操作前后增加日志,如发现确实没有COMMIT,需要排查在事务执行过程(包含前置signal、执行SQL、后置signal等处理)中,是否出现异常,以及数据库连接在中途有没有被其他线程使用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/144025.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C#/.Net开发chatGPT、openAI

C#/.Net开发chatGPT、openAI 最近ChatGPT火爆了,自己使用了一下,确实厉害。但是使用官方网站不支持国内访问,好在国内可以使用API调用,所以自己折腾一番,使用C#来调用API。 获取Token 注册账号获取api-keys等操作可…

HR软件如何人性化管理提高留存率

一直以来,我们都在强调“人是企业最大的资产”,这应该是新时代下,企业对于人才管理的共识。在这样的环境中,人力资源部门承担着重要的责任,HR需要迎合当前的人才需求和管理趋势,在“技术运用”和“人性管理…

激活函数、Sigmoid激活函数、tanh激活函数、ReLU激活函数、Leaky ReLU激活函数、Parametric ReLU激活函数详细介绍及其原理详解

相关文章 梯度下降算法、随机梯度下降算法、动量随机梯度下降算法、AdaGrad算法、RMSProp算法、Adam算法详细介绍及其原理详解反向传播算法和计算图详细介绍及其原理详解激活函数、Sigmoid激活函数、tanh激活函数、ReLU激活函数、Leaky ReLU激活函数、Parametric ReLU激活函数…

从技术、产品、运营3个维度详尽阐述从0到1搭建用户画像系统

通过阅读以下原文获取下载链接。 ​​​​​从技术、产品、运营3个维度详尽阐述从0到1搭建用户画像系统 1 简介 本书借助数据仓库实现一套用户画像系统的方案。从实际工程案例出发,结合多业务场景,内容涵盖开发离线批处理计算的标签及流式计算标签&am…

【C语言进阶】 带你玩转指针

指针进阶一、字符指针二、指针数组三、数组指针3.1:数组指针的使用四、数组参数、指针参数4.1:一维数组传参4.2:二维数组传参4.3:一级指针传参:4.4:二级指针传参:五:函数指针一、字符…

直播 | StarRocks 联合腾讯云分享 EMR-StarRocks 的降本增效之路

极速湖仓(Lakehouse)是 StarRocks 构建极速统一新范式的核心支点。 用户无需将数据导入到 StarRocks 中,无需构建昂贵的数仓,即可实现亚秒级的查询速度,统一分析离线和实时数据,全面发挥湖仓架构的潜在优势…

让国外软件也害怕,4款国产黑科技软件,功能强大且实用

国外月亮不一定比国内圆,国外的软件也不一定比国内好,下面几款软件功能强大到离谱,甚至比国外同款软件更好用。 1、Dism 这是一款妥妥的良心电脑系统优化工具,虽然英文名字,却是由热爱软件的大学生开发,属于…

Android 使用dx/d8将jar转换为dex

前言 在 JDK1.8 之前我们可以通过 dx 工具将 jar 转为 dex。 为了能够支持 JDK1.8,目前 AGP 是通过在 D8/R8 将 class 文件编译成 dex 文件的过程中,对字节码进行转换来实现的,这个转换过程称为脱糖。 因此 JDK1.8 及以后我们需要使用 d8 工…

计算机网络笔记之物理层

文章目录比特、比特率,码元率(波特率)通信方式之单工、半双工和双工介质访问控制数据、信号、模拟信号、数字信号传输介质多模光纤和单模光纤光纤的优点无线传输卫星通信的特点参考计算机网络笔记之网络概论 物理层的主要功能是规定了接口的…

如何优化 order by 语句

order by 查询语句使用也是非常频繁,有时候数据量大了会发现排序查询很慢,本文就介绍一下 MySQL 是如何进行排序的,以及如何利用其原理来优化 order by 语句。 建立一张表: CREATE TABLE cc4 (id INT(11) NOT NULL,user_name VA…

reactHooks+TS:富文本braft-editor常见用法

react富文本braft-editor使用 阅读文档推荐:https://www.yuque.com/braft-editor/be、https://braft.margox.cn/demos/code-highlighter、 1、安装 # 使用npm安装 npm install braft-editor --save# 使用yarn安装 yarn add braft-editor2、基本使用 第一步&…

FGH60N60SMD 60A600V IGBT单管在工业逆变应用中的解决方案

场截止IGBT单管 60A600V FGH60N60SMD 采用新型场截止 IGBT 技术,为太阳能逆变器、UPS、焊接机、电信、ESS 和 PFC 等低导通和开关损耗至关重要的应用。 ONsemi安森美IGBT单管系列: FGH40N60SMD FGH60N60SMD FGH75T65SHD-F155 NGTB40N120FL2WG特性&…

基于pytorch的CREStereo立体匹配算法

文章目录前言一、CREStereo是什么?1.自适应群相关层局部特征注意力2D-1D转换局部搜索可变形的搜索窗口Group-wise相关性2.级联的网络3.叠加级联推理4.叠加级联推理损失函数二、基于pytorch的CREStereo立体匹配算法前言 CREStereo目前在middlebury上的排名第三&#…

Java高手速成 | Spring、JPA与Hibernate的整合

01、设置Spring的配置文件 在Spring的配置文件applicationContext.xml中&#xff0c;配置C3P0数据源、EntityManagerFactory和JpaTransactionManager等Bean组件。以下是applicationContext.xml文件的源程序。 /* applicationContext.xml */ <?xml version"1.0" …

【财务】FMS财务管理系统---审计流程

本文是电商系列的终结篇&#xff0c;笔者在本文介绍了审计流程及注意事项。 本篇是介绍财务审计的&#xff0c;作为电商系列的终结篇。后续计划去完成供应链后台的相关系统的梳理与学习&#xff0c;非常感谢朋友们在阅读过程中提出的问题与建议。 一、审计及流程 财务审计是每…

吴晓波年终秀原版PPT下载

省时查报告-专业、及时、全面的行研报告库省时查方案-专业、及时、全面的营销策划方案库【免费下载】2022年11月份热门报告盘点2023年&#xff0c;如何科学制定年度规划&#xff1f;罗振宇2023年跨年演讲PPT原稿《底层逻辑》高清配图清华大学256页PPT元宇宙研究报告.pdf&#x…

nginx学习笔记2(小d课堂)

nginx目录文件讲解&#xff1a; 我们这里要去了解我们画红框了的这四个目录。 我们一般只用这两个文件&#xff0c;nginx.conf.default是nginx的默认模板。 我们先来看看这个默认模板&#xff1a; 这里面会有特别多的配置&#xff0c;我们后面的课会去学到。我们可能以后改哪个…

【实战篇】37 # 如何使用 QCharts 图表库绘制常用数据图表?

说明 【跟月影学可视化】学习笔记。 QCharts 图表库 QCharts 是一个基于 spritejs 封装的图表库&#xff0c;可以让用户以组件的形式组合出各种图表&#xff1a;https://www.qcharts.cn/#/home QCharts 图表的基本用法 最简单的方式是&#xff0c;直接通过 CDN&#xff0c;…

Mac 几款不错的文件管理工具

Default Folder X 文件快捷访问工具 Default Folder X V6.9d19 是一款 Mac 上的文件夹快捷访问工具&#xff0c;您可以访问您最近的&#xff0c;最喜欢的&#xff0c;并打开文件夹的默认文件夹X的工具栏右侧的内容。扩大你把鼠标移到他们的层次弹出菜单&#xff0c;让您浏览您…

【胖虎的逆向之路】动态加载和类加载机制详解

胖虎的逆向之路 —— 动态加载和类加载机制详解一、前言二、类的加载器1. 双亲委派模式2. Android 中的类加载机制1&#xff09;Android 基本类的预加载2&#xff09;Android类加载器层级关系及分析3&#xff09;BootClassLoader4&#xff09;Class文件加载5&#xff09;PathCl…