覆盖Django mysql model中save方法时碰到的一个数据库更新延迟问题

本文探讨了在Django中通过admin后台更新数据时遇到的问题:更新后的数据未能立即反映在缓存中。作者尝试了多种解决办法,最终通过启动独立线程异步更新缓存的方式解决了问题。

最近有一个需求,通过django的admin后台,可以人工配置5张表的数据,这些数据进行一些业务规则处理后会统一成一份数据缓存在一个cache之中供服务端业务访问,因而任何一张表的数据更新(增、删、改),都要需要重新根据规则计算数据结果,并更新cache。

首先想到的方法就是覆盖每个表model子类中的save方法,在其中先调用父类的save方法走原有保存逻辑更新数据到数据库后,之后再单独调用一次cache的更新逻辑,这样每张表的任意数据被用户更新后,都将先触发model的数据库更新、而后执行cache的数据更新,其中Application表model的代码如下所示:

 1 class Application(models.Model):
 2     name = models.CharField(max_length=128, blank=False, verbose_name=u'应用名')
 3     description = models.TextField(blank=False, verbose_name=u'应用描述')
 4     status = models.IntegerField(verbose_name=u'状态', choices=APPLICATION_STATUS)
 5     mtime = models.DateTimeField(blank=False, verbose_name=u'修改日期', auto_now=True)
 6     ctime = models.DateTimeField(blank=False, verbose_name=u'创建日期', auto_now_add=True)
 7     class Meta:
 8         db_table = 'application'
 9         verbose_name_plural = u'应用'
10 
11     def save(self, *args, **kargs):
12         super(Application, self).save(*args, **kargs)
13         # 更新memcached逻辑实现函数,该函数为通用函数一部分,会单独建立mysql连接,查询数据库数据,并更新到memcached
14         update_memcached_from_mysql()

这样每次在web上新增或者修改数据表记录时,都会先执行父类save操作,save完成后,又会执行update_memcached_from_mysql函数,从mysql查询到最新数据,而后更新到cache之中了。

然而实际测试的时候,发现每次修改数据时,更新到cache的并不是最新数据,而是未修改前的旧数据,比如当前name="test0",修改为name="test1"点击保存后,更新到cache之中的确还是test0,再次修改为name="test2",更新到cache之中的确实test1。

百思不得其解~怀疑是model执行save时,本地有cache会延迟更新,于是在super.save和update_memcached_from_mysql之间增加了time.sleep(10),并多次调用update_memcached_from_mysql函数,可是依然是每次修改保存时,更新到cache的数据都是修改前的取值:

1     def save(self, *args, **kargs):
2         super(Application, self).save(*args, **kargs)
3         # 更新memcached逻辑实现函数,该函数为通用函数一部分,会单独建立mysql连接,查询数据库数据,并更新到memcached
4         update_memcached_from_mysql()
5         time.sleep(10)
6         update_memcached_from_mysql()
7         time.sleep(10)
8         update_memcached_from_mysql()

想不出好的解决方案,猜测model真正将数据更新到数据的时机是在save整个函数执行结束后,臆测了如下更新逻辑:

1 子类save执行前
2 父类save执行
3 更新memcached
4 子类save执行结束
5 真正更新到数据库

于是必须想办法将第3步的cache更新逻辑挪到save执行结束后,然后要保证每次执行save操作时更新cache,这个位置又不能动~~

于是考虑通过开启独立线程异步执行的方式实现,改写update_memcached_from_mysql,在其中开启独立线程执行一个delay版本的更新函数,线程start后会先休眠n秒钟(n为可控参数,下例中为2),而后才执行从数据库读取数据并更新到cache的逻辑,改完后手动更新数据多次,验证已经能拉取到最新数据。

1 def update_memcached_from_mysql():
2     """ 
3     猜测由于model的缓存机制,save函数执行完成前,新的数据可能未及时更新到数据库,
4     此处开启独立线程执行memcache更新操作,线程中会休眠数秒再从数据库拉取最新数据更新
5     """
6     td = threading.Thread(target=update_memcached_from_mysql_delay, args=(2, ))
7     td.start()

然而之前的更新流程还仅仅是猜测而已,虽然采用线程异步延迟更新cache的方法后,多次修改验证避开了取不到新数据的问题,并不就说明猜测一定是正确的,而且即便猜测是正确的,如果save函数执行完后,model的数据更新没有在线程延迟时间结束前完成,理论上还是会有问题,考虑可以通过设置一个定时任务,比如每隔10分钟定时执行cache更新逻辑,来保证新数据最多延迟10分钟也能生效。

本来想深入探究model save更新机制~然而最近太忙了~~blog都两周没更新了,初步尝试了一下也还没有研究清楚这一块save逻辑的源码,这个数据修改平台也仅供内部使用~~暂时先这么修补一下~~以后有时间再深究这一块的问题~~加入TODO list。

 

转载于:https://www.cnblogs.com/AcAc-t/p/django_model_save_delay.html

<think>好的,我现在需要解决用户的问题:在DjangoMySQL中实现销售表操作后自动更新库存表数据的方法或代码示例。用户希望了解如何通过Django模型和可能的触发器来自动同步这两个表的数据。 首先,我需要回顾Django的信号机制,因为这是Django中处理模型操作后自动执行代码的常用方法。记得Django提供了pre_save、post_save、pre_delete、post_delete等信号,可以在模型保存或删除前后触发自定义的函数。用户的需求是在销售表更新触发库存变化,所以post_save信号可能适用,但需要考虑创建和更新的不同情况。 接下来,库存更新的逻辑。假设销售表中有产品、数量等字段,当新增或修改销售记录,库存表中的对应产品库存需要相应减少或调整。例如,当创建销售记录,库存减少销售数量;当更新销售记录中的数量,需要计算新旧数量的差值来调整库存;当删除销售记录,库存需要恢复被删除的数量。因此,处理post_save和post_delete信号是关键。 然后,考虑模型的关联。销售模型应该有一个外键指向产品模型,而库存模型也可能与产品模型相关联。需要确保销售模型和库存模型之间的正确关联,以便通过产品来找到对应的库存记录。 还需要注意并发问题。如果多个销售操作同发生,可能会导致库存竞争条件。Django的F表达式可以在数据库层面进行原子操作,避免在并发情况下数据不一致的问题。比如使用F('stock') - quantity来更新库存,而不是在Python中计算。 另外,是否需要使用数据库触发器?虽然Django的信号可以在应用层处理,但有为了数据一致性,可能在数据库层使用触发器更可靠,特别是当有多个应用访问数据库。不过用户主要使用Django,所以可能更倾向于用Django的信号来实现,这样维护代码更方便。 现在,我需要检查用户提供的引用内容。引用[1]提到Django项目的初始化,引用[2]和[3]涉及数据库设计,特别是MySQL的优势。这些引用可能提示用户已经在使用DjangoMySQL,需要结合这两者来实现功能。 接下来,构建示例模型。销售模型Sale可能包含product(外键到Product模型)、quantity、sale_date等字段。库存模型Inventory同样关联Product,并记录stock数量。在Django中定义这些模型后,通过信号连接它们。 编写信号处理函数,需要处理创建、更新和删除销售记录的情况。在post_save信号中,判断是否是新建实例还是更新,如果是更新,需要获取之前的数量来计算差值。这可能需要查询数据库中的旧值,或者使用pre_save信号来捕获旧值。 此外,使用transaction.atomic来确保数据操作的原子性,避免部分更新导致的数据不一致。同,异常处理也很重要,比如库存不足抛出异常,回滚事务。 最后,是否需要使用MySQL的触发器?如果需要,可以在数据库中创建AFTER INSERT或AFTER UPDATE的触发器,但这样会脱离Django的ORM,可能增加维护难度。不过用户的问题中提到希望可能的代码示例,所以可能需要两种方法的示例,但优先使用Django的信号。 综上,步骤应该是:定义模型,创建信号处理函数,连接信号,并使用F表达式来安全更新库存。同,考虑事务和异常处理,确保数据一致性。</think>在Django中实现销售表更新自动触发库存变化,可以通过模型信号(Signals)结合数据库事务来实现。以下是具体实现方法和代码示例: ### 一、模型定义 ```python # models.py from django.db import models from django.db.models import F class Product(models.Model): name = models.CharField(max_length=100) class Inventory(models.Model): product = models.OneToOneField(Product, on_delete=models.CASCADE) stock = models.PositiveIntegerField(default=0) class Sale(models.Model): product = models.ForeignKey(Product, on_delete=models.CASCADE) quantity = models.PositiveIntegerField() sale_date = models.DateTimeField(auto_now_add=True) ``` ### 二、信号处理实现 ```python # signals.py from django.db.models.signals import post_save, post_delete from django.dispatch import receiver from django.db import transaction from .models import Sale, Inventory @receiver(post_save, sender=Sale) def update_inventory_on_save(sender, instance, created, **kwargs): """ 销售记录创建/更新自动更新库存 处理逻辑: 1. 新建销售记录:库存直接减少 2. 修改销售数量:计算新旧差值调整库存 """ with transaction.atomic(): if created: delta = instance.quantity else: old_quantity = Sale.objects.get(pk=instance.pk).quantity delta = instance.quantity - old_quantity Inventory.objects.filter(product=instance.product).update( stock=F('stock') - delta ) @receiver(post_delete, sender=Sale) def update_inventory_on_delete(sender, instance, **kwargs): """ 删除销售记录恢复库存 """ with transaction.atomic(): Inventory.objects.filter(product=instance.product).update( stock=F('stock') + instance.quantity ) ``` ### 三、信号注册 ```python # apps.py from django.apps import AppConfig class SalesConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'sales' def ready(self): import sales.signals # 确保信号处理器被加载 ``` ### 四、MySQL触发器实现(可选) ```sql DELIMITER // CREATE TRIGGER after_sale_insert AFTER INSERT ON sales_sale FOR EACH ROW BEGIN UPDATE sales_inventory SET stock = stock - NEW.quantity WHERE product_id = NEW.product_id; END; // CREATE TRIGGER after_sale_update AFTER UPDATE ON sales_sale FOR EACH ROW BEGIN UPDATE sales_inventory SET stock = stock + (OLD.quantity - NEW.quantity) WHERE product_id = NEW.product_id; END; // DELIMITER ; ``` ### 实现要点说明: 1. **原子操作**:使用`F()`表达式进行原子更新,避免并发问题[^3] 2. **事务保障**:通过`transaction.atomic()`确保数据一致性 3. **信号机制**:post_save信号区分新建/更新操作[^1] 4. **库存校验**:建议在信号处理器中添加库存不足校验逻辑
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值