Django框架ORM操作數(shù)據(jù)庫(kù)不生效問(wèn)題示例解決方法
本文詳細(xì)描述使用Django 的ORM框架操作PostgreSQL數(shù)據(jù)庫(kù)刪除不生效問(wèn)題的定位過(guò)程及解決方案,并總結(jié)使用ORM框架操作數(shù)據(jù)庫(kù)不生效的問(wèn)題的通用定位方法
問(wèn)題描述
最近使用Django 的ORM框架操作PostgreSQL數(shù)據(jù)庫(kù)總是出現(xiàn)刪除不生效(尤其是在并發(fā)的時(shí)候)。業(yè)務(wù)代碼中也沒(méi)有任何報(bào)錯(cuò)。
定位過(guò)程 首先,我們懷疑是SQL語(yǔ)句拼裝錯(cuò)誤(比如ID不對(duì)),導(dǎo)致了刪除不生效
通過(guò)在Python日志中打印ORM框架的SQL以及返回的操作結(jié)果,發(fā)現(xiàn)delete操作返回的記錄數(shù)是1。且SQL中的ID符合業(yè)務(wù)邏輯,說(shuō)明相應(yīng)SQL語(yǔ)句是執(zhí)行成功的。排除了這條猜測(cè)
接著我們懷疑DELETE操作后,數(shù)據(jù)又被其他業(yè)務(wù)CREATE回來(lái)了
通過(guò)在數(shù)據(jù)庫(kù)中增加觸發(fā)器,將nfinst表的寫(xiě)操作記錄到nfinst_audit表,發(fā)現(xiàn)沒(méi)有刪除操作。排除了這條猜測(cè)
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;
--創(chuàng)建觸發(fā)器
create trigger nfinst_audit
before insert or update or delete on nfinst
for each row execute procedure process_nfinst_audit();- 結(jié)合以上2點(diǎn),猜測(cè)是事務(wù)沒(méi)有commit導(dǎo)致
Django默認(rèn)的事務(wù)模式是autocommit,每一次數(shù)據(jù)庫(kù)操作執(zhí)行后都會(huì)自動(dòng)提交。項(xiàng)目使用的SQLAlchemy庫(kù)的StaticPool連接池,配合gevent使用,一個(gè)進(jìn)程中的所有協(xié)程串行復(fù)用一個(gè)數(shù)據(jù)庫(kù)連接。
(這里解釋一下為什么要一個(gè)進(jìn)程中的所有協(xié)程復(fù)用一個(gè)連接,因?yàn)镻ython的PostgreSQL驅(qū)動(dòng)pyscopg2是由c語(yǔ)言編寫(xiě),協(xié)程在與數(shù)據(jù)庫(kù)交互時(shí),并不會(huì)因?yàn)閕o操作而切走,所以即使使用多個(gè)連接,也無(wú)法帶來(lái)并發(fā)能力的提升,反而會(huì)增加維護(hù)多個(gè)連接的消耗)
查看delete操作的源碼,delete操作是在一個(gè)事務(wù)中執(zhí)行了pre_delete signal、刪除表記錄、post_delete signal等操作,執(zhí)行完成后自動(dòng)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()
# 開(kāi)啟事務(wù),語(yǔ)句塊執(zhí)行結(jié)束后會(huì)根據(jù)執(zhí)行結(jié)果選擇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:
# 執(zhí)行post_delete后置處理
signals.post_delete.send(
sender=model, instance=obj, using=self.using
)這里的pre_delete signal跟post_delete signal類(lèi)似于數(shù)據(jù)庫(kù)的觸發(fā)器,不過(guò)是在Python代碼層面實(shí)現(xiàn)的。問(wèn)題就出在這個(gè)post_delete signal上面,出錯(cuò)的數(shù)據(jù)表注冊(cè)了post_delete signal,并在其中調(diào)用了REST接口,而調(diào)用REST接口會(huì)導(dǎo)致協(xié)程發(fā)生切換,如果切換后的協(xié)程也操作了數(shù)據(jù)庫(kù),會(huì)將現(xiàn)有的事務(wù)回滾。(因?yàn)閺倪B接池新拿到的連接,應(yīng)該保證是沒(méi)有事務(wù)在執(zhí)行的,如果有,就認(rèn)為該連接上一次被使用時(shí)出現(xiàn)了異常,需回滾事務(wù))
將post_delete相關(guān)邏輯注掉后,問(wèn)題消失
解決方案
解決方法有如下幾種:
- 直接修改Django源碼,將post_delete signal的邏輯移除到事務(wù)外面(Django將post_delete的邏輯放在事務(wù)里確認(rèn)有點(diǎn)坑,一旦post_delete出現(xiàn)異常就會(huì)導(dǎo)致事務(wù)回滾,并且事務(wù)過(guò)長(zhǎng)也會(huì)消耗數(shù)據(jù)庫(kù)資源)
- 修改業(yè)務(wù)代碼,將delete成功后的處理邏輯由使用signal完成,改為重寫(xiě)Django Model的delete方法(先調(diào)用父類(lèi)的delete方法,成功后再執(zhí)行后置處理邏輯)
- 重寫(xiě)signal機(jī)制,post_delete使用自己實(shí)現(xiàn)的signal機(jī)制
最終綜合考慮對(duì)業(yè)務(wù)代碼的侵入性,以及后續(xù)的可維護(hù)性,我們選擇了方案3來(lái)解決數(shù)據(jù)庫(kù)刪除不生效的問(wèn)題。但在實(shí)施的時(shí)候,又發(fā)現(xiàn)了新的問(wèn)題:django從數(shù)據(jù)庫(kù)刪除完數(shù)據(jù)后,會(huì)將Model對(duì)象也刪除,從而導(dǎo)致post_delete無(wú)對(duì)象可操作??紤]到delete操作幾乎不會(huì)出現(xiàn)rollback的情況,將post_delete移到了實(shí)際delete操作前面,類(lèi)似于pre_delete。沒(méi)有直接使用pre_delete是為了減少對(duì)業(yè)務(wù)代碼的入侵。另外django自帶的pre_delete也在事務(wù)中,而我們的改法是將signal操作移到事務(wù)外,以降低數(shù)據(jù)庫(kù)壓力
在models.py中做了如下修改
- 定義了自己的post_delete,并將業(yè)務(wù)代碼中注冊(cè)post_delete信號(hào)量改為從models.py導(dǎo)入post_delete變量
post_delete = ModelSignal(providing_args=["instance", "using"], use_caching=True)
Django Model有2種方式進(jìn)行刪除操作,分別是直接對(duì)一條Model記錄刪除,以及對(duì)QuerySet進(jìn)行刪除。所以需要定義自己的Model類(lèi)以及QuerySet基類(lèi),并讓需要進(jìn)行post_delete操作的Model類(lèi)繼承前面自定義的基類(lèi)
class CModel_QuerySet(models.query.QuerySet):
def delete(self):
# 將post_delete信號(hào)量觸發(fā)操作移到了事務(wù)外面
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基類(lèi)
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信號(hào)量觸發(fā)操作移到了事務(wù)外面
post_delete.send(
sender=self.__class__, instance=self, using=None
)
super(CModelWithUpdateSignal, self).delete(*args, **kwargs)
# 需要進(jìn)行post_delete操作的Model類(lèi)
class NfInstModel(CModelWithUpdateSignal):
……總結(jié)
ORM框架操作數(shù)據(jù)庫(kù),可以抽象至如下流程

如果出現(xiàn)操作數(shù)據(jù)庫(kù)不生效,但是也沒(méi)有報(bào)錯(cuò)的情況??梢詮囊韵聨讉€(gè)方面來(lái)排查問(wèn)題
是否SQL本身執(zhí)行未生效(通常是業(yè)務(wù)邏輯導(dǎo)致,比如DELETE操作傳錯(cuò)了ID),可以在ORM框架源碼中加日志,將SQL執(zhí)行結(jié)果打印出來(lái)
是否本次操作被其他操作覆蓋,可以對(duì)數(shù)據(jù)表增加觸發(fā)器,將CREATE、UPDATE、DELETE操作記錄到另一張表。通過(guò)查看操作記錄來(lái)確認(rèn)是否是業(yè)務(wù)邏輯覆蓋的問(wèn)題
是否是事務(wù)沒(méi)有COMMIT,可以在ORM框架源碼中COMMIT操作前后增加日志,如發(fā)現(xiàn)確實(shí)沒(méi)有COMMIT,需要排查在事務(wù)執(zhí)行過(guò)程(包含前置signal、執(zhí)行SQL、后置signal等處理)中,是否出現(xiàn)異常,以及數(shù)據(jù)庫(kù)連接在中途有沒(méi)有被其他線(xiàn)程使用
到此這篇關(guān)于Django框架ORM操作數(shù)據(jù)庫(kù)不生效問(wèn)題的定位示例的文章就介紹到這了,更多相關(guān)django orm操作數(shù)據(jù)庫(kù)不生效內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python 中不同包 類(lèi) 方法 之間的調(diào)用詳解
這篇文章主要介紹了python 中不同包 類(lèi) 方法 之間的調(diào)用詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-03-03
python網(wǎng)絡(luò)爬蟲(chóng)之協(xié)程的實(shí)現(xiàn)方法
這篇文章主要介紹了python網(wǎng)絡(luò)爬蟲(chóng)之協(xié)程的實(shí)現(xiàn)方法,協(xié)程Coroutine又稱(chēng)微線(xiàn)程,是一種用戶(hù)態(tài)內(nèi)的上下文切換技術(shù),簡(jiǎn)而言之,就是通過(guò)一個(gè)線(xiàn)程實(shí)現(xiàn)代碼塊相互切換執(zhí)行,需要的朋友可以參考下2023-08-08
python隨機(jī)模塊random的22種函數(shù)(小結(jié))
這篇文章主要介紹了python隨機(jī)模塊random的22種函數(shù),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05
tensorflow 報(bào)錯(cuò)unitialized value的解決方法
今天小編就為大家分享一篇tensorflow 報(bào)錯(cuò)unitialized value的解決方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-02-02
Python腳本實(shí)現(xiàn)自動(dòng)刪除C盤(pán)臨時(shí)文件夾
在日常使用電腦的過(guò)程中,臨時(shí)文件夾往往會(huì)積累大量的無(wú)用數(shù)據(jù),占用寶貴的磁盤(pán)空間,下面我們就來(lái)看看Python如何通過(guò)腳本實(shí)現(xiàn)自動(dòng)刪除C盤(pán)臨時(shí)文件夾吧2025-01-01
淺談關(guān)于Python3中venv虛擬環(huán)境
這篇文章主要介紹了淺談關(guān)于Python3中venv虛擬環(huán)境,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-08-08
Python3.5基礎(chǔ)之NumPy模塊的使用圖文與實(shí)例詳解
這篇文章主要介紹了Python3.5基礎(chǔ)之NumPy模塊的使用,結(jié)合圖文與實(shí)例形式詳細(xì)分析了Python3.5中Numpy模塊的原理、功能、使用方法及操作注意事項(xiàng),需要的朋友可以參考下2019-04-04

