網站首頁 編程語言 正文
本文詳細描述使用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相關邏輯注掉后,問題消失
解決方案
解決方法有如下幾種:
- 直接修改Django源碼,將post_delete signal的邏輯移除到事務外面(Django將post_delete的邏輯放在事務里確認有點坑,一旦post_delete出現異常就會導致事務回滾,并且事務過長也會消耗數據庫資源)
- 修改業務代碼,將delete成功后的處理邏輯由使用signal完成,改為重寫Django Model的delete方法(先調用父類的delete方法,成功后再執行后置處理邏輯)
- 重寫signal機制,post_delete使用自己實現的signal機制
最終綜合考慮對業務代碼的侵入性,以及后續的可維護性,我們選擇了方案3來解決數據庫刪除不生效的問題。但在實施的時候,又發現了新的問題:django從數據庫刪除完數據后,會將Model對象也刪除,從而導致post_delete無對象可操作。考慮到delete操作幾乎不會出現rollback的情況,將post_delete移到了實際delete操作前面,類似于pre_delete。沒有直接使用pre_delete是為了減少對業務代碼的入侵。另外django自帶的pre_delete也在事務中,而我們的改法是將signal操作移到事務外,以降低數據庫壓力
在models.py中做了如下修改
- 定義了自己的post_delete,并將業務代碼中注冊post_delete信號量改為從models.py導入post_delete變量
post_delete = ModelSignal(providing_args=["instance", "using"], use_caching=True)
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框架操作數據庫,可以抽象至如下流程
如果出現操作數據庫不生效,但是也沒有報錯的情況。可以從以下幾個方面來排查問題?
是否SQL本身執行未生效(通常是業務邏輯導致,比如DELETE操作傳錯了ID),可以在ORM框架源碼中加日志,將SQL執行結果打印出來
是否本次操作被其他操作覆蓋,可以對數據表增加觸發器,將CREATE、UPDATE、DELETE操作記錄到另一張表。通過查看操作記錄來確認是否是業務邏輯覆蓋的問題
是否是事務沒有COMMIT,可以在ORM框架源碼中COMMIT操作前后增加日志,如發現確實沒有COMMIT,需要排查在事務執行過程(包含前置signal、執行SQL、后置signal等處理)中,是否出現異常,以及數據庫連接在中途有沒有被其他線程使用
原文鏈接:https://blog.csdn.net/alex_i/article/details/128581367
相關推薦
- 2022-07-03 python使用open函數對文件進行處理詳解_python
- 2022-03-18 c語言的指針數組詳解(c語言指針與數組)
- 2022-09-25 2022react高頻面試題有哪些
- 2022-06-17 Python正則表達式的小練習分享_python
- 2022-03-22 .NET?6開發TodoList實現請求日志組件HttpLogging_實用技巧
- 2022-12-19 C++?Boost?Fusion創建異構容器詳解_C 語言
- 2023-02-17 GO項目實戰之Gorm格式化時間字段實現_Golang
- 2023-01-26 使用Pandas修改DataFrame中某一列的值_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支