前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >可重复读事务隔离级别之 django 解读

可重复读事务隔离级别之 django 解读

原创
作者头像
梅海峰
修改2017-08-22 09:46:41
1.7K0
修改2017-08-22 09:46:41
举报
文章被收录于专栏:梅海峰的专栏梅海峰的专栏

事务作为并发访问数据库一种有效工具,如果使用不当,也会引起问题。mysql是公司内使用的主流数据库,默认事务隔离级别是可重复读。

本文尝试结合django解释应用开发中并发访问数据库可能会遇到的可重复读引起的问题,希望能帮助大家在开发过程中有效避免类似问题,如果老版本应用中出现这类问题也可以快速定位。

由于django1.3(由于历史原因,目前蓝鲸体系内大多数稳定运营的工具系统用的是django1.3)中该问题最为严重,本文先对django1.3环境中的一个应用案例进行分析,说明问题产生的具体原因,然后说明如何有效避免类似问题,最后介绍较新版本django中事务实现原理(django1.6开始已经很好避免本文案例中的大多数情况),并提供一个django1.8中由于对事务使用不当造成的异常案例。

先看下如下这段代码在django1.3中会有什么问题:

代码语言:txt
复制
class MyData(models.Model):
    key = models.CharField(primary_key=True, max_length=64)
    value = models.FloatField()

def simple_test(request):
    key = str(uuid.uuid4())
    set_data_in_backend.apply_async(args=(key, ))
    sleep(1)  # do something
    obj, r = MyData.objects.get_or_create(key=key, defaults={"value": 1})
    return HttpResponse(str(r))

@task  # celery task
def set_data_in_backend(key):
    obj, r = MyData.objects.get_or_create(key=key, defaults={"value": 0})
    return r

通过链接http://127.0.0.1:8000/simple_test/请求得到的结果是500错误, 如果开启了debug,则可以看到如下错误信息:

代码语言:txt
复制
IntegrityError at /simple_test/
(1062, "Duplicate entry '6d8587ff-d983-4fd3-baab-a987faf4ae78' for key 1")
...

这个执行结果有点让人吃惊,本应该返回False才对。

为了快速说明该问题产生的原因,这里将请求simple_test过程中simple_test和后台任务set_data_in_backend所执行的sql语句分别打印出来:

simple_test响应请求过程执行的sql:

代码语言:txt
复制
set autocommit: False
query: SELECT `django_session`.`session_key`, `django_session`.`session_data`, `django_session`.`expire_date` FROM `django_session` WHERE (`django_session`.`session_key` = '4279838c2ca84586ff76514491ed457b'  AND `django_session`.`expire_date` > '2016-08-07 02:31:04' )
query: SELECT `auth_user`.`id`, `auth_user`.`username`, `auth_user`.`first_name`, `auth_user`.`last_name`, `auth_user`.`email`, `auth_user`.`password`, `auth_user`.`is_staff`, `auth_user`.`is_active`, `auth_user`.`is_superuser`, `auth_user`.`last_login`, `auth_user`.`date_joined` FROM `auth_user` WHERE `auth_user`.`id` = 1 
query: SELECT `home_application_mydata`.`key`, `home_application_mydata`.`value` FROM `home_application_mydata` WHERE `home_application_mydata`.`key` = 'd180d7c3-c23b-4940-a5b5-9381e835b7bd' 
query: INSERT INTO `home_application_mydata` (`key`, `value`) VALUES ('d180d7c3-c23b-4940-a5b5-9381e835b7bd', 1)
query: SELECT `home_application_mydata`.`key`, `home_application_mydata`.`value` FROM `home_application_mydata` WHERE `home_application_mydata`.`key` = 'd180d7c3-c23b-4940-a5b5-9381e835b7bd' 
query: SELECT `home_application_mydata`.`key`, `home_application_mydata`.`value` FROM `home_application_mydata` LIMIT 21

后台任务set_data_in_backend执行过程中执行的sql语句:

代码语言:txt
复制
set autocommit: False
query: SELECT `home_application_mydata`.`key`, `home_application_mydata`.`value` FROM `home_application_mydata` WHERE `home_application_mydata`.`key` = '6e3247f8-31c5-46d7-a3e9-1c855077ea56'
sql-debugger(connection): INSERT INTO `home_application_mydata` (`key`, `value`) VALUES ('6e3247f8-31c5-46d7-a3e9-1c855077ea56', 0)
commit
query: SELECT `celery_taskmeta`.`id`, `celery_taskmeta`.`task_id`, `celery_taskmeta`.`status`, `celery_taskmeta`.`result`, `celery_taskmeta`.`date_done`, `celery_taskmeta`.`traceback`, `celery_taskmeta`.`hidden`, `celery_taskmeta`.`meta` FROM `celery_taskmeta` WHERE `celery_taskmeta`.`task_id` = 'fd292219-da59-45a4-8b59-89ab1152c20c'
query: INSERT INTO `celery_taskmeta` (`task_id`, `status`, `result`, `date_done`, `traceback`, `hidden`, `meta`) VALUES ('fd292219-da59-45a4-8b59-89ab1152c20c', 'SUCCESS', 'gAKILg==', '2016-08-07 02:35:24', NULL, 0, 'eJxrYKotZIzgYGBgSM7IzEkpSs0rZIotZC7WAwBWuwcA')
commit

结合simple_test响应过程执行的sql语句来看,就比较好理解上面的500错误duplicate entry了。响应开始的时候, 开发框架进行了一次用户登录认证,django设置了autocommit为False,这会直接开启一个事务

这时key=6e3247f8-31c5-46d7-a3e9-1c855077ea56的记录还不存在,由于mysql默认的事务隔离级别是可重复读,因此在simple_test整个事务期间,都找不到key=6e3247f8-31c5-46d7-a3e9-1c855077ea56的记录,所以simple_test执行到get_or_create会尝试插入一条记录key=6e3247f8-31c5-46d7-a3e9-1c855077ea56,但是在此之前后台任务已经向数据库中插入了这个key,simple_test执行get_or_create的时候mysql就给直接报一致性错误。

弄明白了这个异常发生的原理之后,我们可能会吓出一身冷汗,如果写个while循环一直去查询数据库中任务的状态到完成状态,岂不是死循环了。在django1.3中的确是这样,因为这个问题django1.3中的cache框架就被提交了Bug,django1.3遵循的是PEP 249Python数据库API 规范v2.0, 需要将autocommit初试设置为关闭状态。到了Django1.6之后已经覆盖了这个默认规范并且将autocommit设置为 on. 因此新版本的django出现上述问题的概率会大大降低。

我们可能会有些相对稳定运营的django1.3在生产环境,如果真的出现了类似的问题,可以尝试从几个方面修复:

(1)调整中间件,对登录认证完成之后进行一次commit操作。部分因为中间件过早开启事务的情形有用,比如本文的案例。

(2)发生类似错误时,显式进行一次commit操作。这种解决方式比较直观,但是如果错误本身就发生在事务中则会过早提交事务。

(3)如果只是需要把记录拿出来更新,可以考虑直接写sql更新记录。

为了说明django1.8中事务实现机制如何与django1.3不一样,将本文开始时使用案例放在django1.8中执行,调用的sql如下:

代码语言:txt
复制
set autocommit: False
set autocommit: True
query: SET SQL_AUTO_IS_NULL = 0
set autocommit: False
set autocommit: True
query: SET SQL_AUTO_IS_NULL = 0
query: SELECT `django_session`.`session_key`, `django_session`.`session_data`, `django_session`.`expire_date` FROM `django_session` WHERE (`django_session`.`session_key` = 'd0q3afzgeilvh1zbdgkp19misc37dim5' AND `django_session`.`expire_date` > '2016-08-07 03:32:40')
query: SELECT `auth_user`.`id`, `auth_user`.`password`, `auth_user`.`last_login`, `auth_user`.`is_superuser`, `auth_user`.`username`, `auth_user`.`first_name`, `auth_user`.`last_name`, `auth_user`.`email`, `auth_user`.`is_staff`, `auth_user`.`is_active`, `auth_user`.`date_joined` FROM `auth_user` WHERE `auth_user`.`id` = 1
query: SELECT `test_123_mydata`.`key`, `test_123_mydata`.`value` FROM `test_123_mydata` WHERE `test_123_mydata`.`key` = '27ada689-86f4-4192-a0b9-dc6608d74ed9'

从django1.8中执行的sql可以看出,Django1.8的默认行为是运行在自动提交模式下。任何一个查询都立即被提交到数据库中,除非显示激活一个事务。最后,django1.8只是将这种可重复读引起问题的概率降低了很多,如果我们在事务中处理不当,也会引起类似问题,django本文最开始的例子进行稍微调整,在django1.8中运行一样会报错。

代码语言:txt
复制
@atomic
def simple_test(request):
    keys = list(MyData.objects.values("key"))
    key = str(uuid.uuid4())
    set_data_in_backend.apply_async(args=(key, ))
    sleep(1)  # do something
    obj, r = MyData.objects.get_or_create(key=key, defaults={"value": 1})
    return HttpResponse(str(r))

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 SQL Server
腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档