zoukankan      html  css  js  c++  java
  • 如何在 Django 中保证并发的数据一致性

    1. 关于锁

    1.1 乐观锁

    乐观锁的出发点是,同一条数据很少会因为并发修改而产生冲突,适用于读多写少的场景,用以提高吞吐量。

    实现方式,读取一个字段,执行处理逻辑,当需要更新数据时,再次检查该字段是否和第一次读取一致。如果一致,则更新数据,否则拒绝更新,重新读取后再提交。

    1.2 悲观锁

    悲观锁的出发点是,当一条数据正在被修改时,不允许其他任何关于这条数据的操作。

    实现方式,读取一个字段之后,加锁,不允许其他任何读、写操作。执行处理逻辑,更新数据完毕后,释放锁。

    1.3 比较

    乐观锁的开销远低于悲观锁。

    悲观锁可能会导致死锁。当 A 锁定了 a 资源,需要 b 资源。而 b 被 B 锁定,正在等待 a 资源。此时,导致出现死锁。但是,可以通过设置超时来处理这个问题。

    悲观锁可以有效降低冲突后,重试的次数。

    乐观锁可以提高响应速度。

    2. Django 中的事务

    Django 默认每条数据库操作都会被立即提交到数据库。

    这样会导致一个问题,如果有一系列的数据库操作构成,要么全部执行,要么就全部都不执行,怎么办?

    这时,就需要事务。将一系列数据库操作设置为一个事务,提交给数据库执行。

    Django 提供 atomic 装饰器以开启事务。 atomic 使用一个参数来指定数据库的名字。如果不设置值,Django 就会使用系统默认的数据库。

    2.1 整个 View 函数开启事务

    from django.db import transaction
    
    @transaction.atomic
    def viewfunc(request):
        # This code executes inside a transaction.
        do_stuff()

    2.2 部分函数 do_more_stuff() 开启事务。

    from django.db import transaction
    
    def viewfunc(request):
        # This code executes in autocommit mode (Django's default).
        do_stuff()
    
        with transaction.atomic():
            # This code executes inside a transaction.
            do_more_stuff()

    2.3 不要在事务中处理异常

    from django.db import transaction
    
    def viewfunc(request):
        do_stuff()
        try:
            with transaction.commit_on_success():
                do_more_stuff_1() # in transaction
                try:
                    do_more_stuff_2() # not in transaction
                except:
                    pass
                do_more_stuff_3() # in transaction
        except:
            pass

    当退出原子块时,Django 会查看它是否正常退出或者是否有异常来确定是否提交或者回滚。

    如果你捕获并处理原子块中的异常,可以能会隐藏 Django 中发生问题的事实。这样可能会造成非预期的行为。

    3. 利用 F 函数更新运算

    通常更新数据库的操作,需要将对象读取到内存。在内存中进行修改之后,再写回数据库。

    在内存中的操作,如果存在同时操作的情况,会导致运算逻辑错误。

    F() 函数的作用就是直接生成 SQL 语句,不必将需要更新的对象读取到内存。避免了并发导致的数据不一致问题。

    from django.db.models import F
    
    reporter = Reporters.objects.get(name='OICQ')
    reporter.stories_filed = F('stories_filed') + 1
    reporter.save()

    4. 利用 select_for_update 函数

    select_for_update 使用的是悲观锁。

    select for update 函数使用数据库查询语句,select ... for update 对数据库进行操作。

    这是数据库层面的,解决并发取数据后再修改的问题方法。

    def mark_as_readed(self, notification_id):
        # 让s elect for update 和 update 语句发生在一个完整的事务里面
        with transaction.commit_on_success():
            # 使用select_for_update 来保证并发请求同时只有一个请求在处理,其他的请求
            # 等待锁释放
            notification = Notification.objects.select_for_update().get(pk=notification_id)
            # 没有必要重复标记一个已经读过的通知            
            if notication.has_readed:                
                return
            notification.has_readed = True
            notification.save()
            # 在这里更新我们的计数器,嗯,我感觉好极了
            self.update_unread_count(-1)
  • 相关阅读:
    链表--反转链表(leetcode 206
    链表--重排链表(leetcode 143
    链表--删除链表中的结点(leetcode 237
    链表--K个一组反转链表(leetcode 25
    链表--相交链表(leetcode 160
    链表--两数相加II(leetcode 445
    链表--复制含有随机指针节点的链表(leetcode138
    链表--回文链表(leetcode234
    链表--环形链表(leetcode 141,142
    链表--分隔链表(leetcode86
  • 原文地址:https://www.cnblogs.com/zhaoyingjie/p/9239583.html
Copyright © 2011-2022 走看看