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

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

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

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

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

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

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

代码语言:txt
AI代码解释
复制
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
AI代码解释
复制
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
AI代码解释
复制
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
AI代码解释
复制
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
AI代码解释
复制
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
AI代码解释
复制
@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 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
数据库事务隔离级别
在并发的场景中,为了保证数据的一致性我们会在数据库中使用事务。然而在强一致性与性能上则需要根据具体业务来取舍,所以一般数据库提供了四种事务隔离级别: 读未提交(Read Uncommitted) 读提交(Read Committed) 可重复读(Repeatable Read) 序列化(Serializable) 由于日常工作中使用事务比较频繁,遂在此作一下总结 在了解这四种事务隔离级别之前,需要了解如下概念: 更新丢失(Lost Update): 两个事务同时修改一行数据,其中一个事务的更新被另外一个事
码农二狗
2018/06/29
5610
mysql事务隔离级别与MVCC
事务A和事务B都读取了同一行数据, 比如原数据行的值是100,事务A是将数值读取出来+1并更新, 事务B是读取数值+2并更新。当事务A和事务B都读取到了100,事务A首先完成并更新为101,事务B随后完成更新成了102。这样事务B就把事务A的结果覆盖了。
leobhao
2022/06/28
4170
mysql事务隔离级别与MVCC
Navicat 环境测试 innodb 的事务隔离级别 产生的幻读 和 不可重复读
自己被 事务隔离级别 及产生的错误 烦扰太多次了,最近在集中学mysql 要解决一下,mysql 的问题并重视起来。 所以记录一下 实验的过程:
猎户星座1
2020/10/20
2K0
【Mysql】一文解读【事务】-【基本操作/四大特性/并发事务问题/事务隔离级别】
YY的秘密代码小屋
2024/04/03
3050
【Mysql】一文解读【事务】-【基本操作/四大特性/并发事务问题/事务隔离级别】
innodb之事务隔离级别示例
下面我们用真实的例子来说明各个级别的情况,首先我们创建一个数据库test,然后再数据库中创建一个表city,在这个city表中来进行测试:
AsiaYe
2019/11/06
4380
Mysql为何使用可重复读(Repeatable read)为默认隔离级别?
群里有小伙伴面试时,碰到面试官提了个很刁钻的问题:Mysql为何使用可重复读(Repeatable read)为默认隔离级别??? 下面进入正题: 我们都知道事务的几种性质 :原子性、一致性、隔离性和
Java宝典
2021/07/15
1.9K0
翻译了Django1.4数据库访问优化部分
rst生成的html5在线ppt下载:http://www.kuaipan.cn/file/id_12834302878348970.htm
the5fire
2019/02/28
6840
InnoDB中的事务隔离级别与锁
一个事务在进行数据变更时对另一个事务产生的可见性影响描述,表达为 脏读、幻读、不可重复读三个概念。下面具体解释下对应概念。
知一
2021/12/07
7320
InnoDB中的事务隔离级别与锁
面试突击61:说一下MySQL事务隔离级别?
MySQL 事务隔离级别是为了解决并发事务互相干扰的问题的,MySQL 事务隔离级别总共有以下 4 种:
磊哥
2022/06/30
3090
面试突击61:说一下MySQL事务隔离级别?
MySQL事务(一)MySQL事务隔离级别、锁机制
数据库通常会同时执行多个事务,这些事务可能同时对同一批数据进行增删改查操作,可能会导致脏写、脏读、不可重复读和幻读等问题。
鳄鱼儿
2024/05/21
1.5K0
MySQL事务(一)MySQL事务隔离级别、锁机制
MySQL事务隔离级别
这四种级别由上至下,隔离强度逐渐增强,性能逐渐变差。它们没有绝对的优劣,采取哪种应该根据系统需求决定。MySQL默认级别为:可重复读。
Libertyyyyy
2022/10/25
1.3K0
MySQL 慢查询、 索引、 事务隔离级别
MySQL 的慢查询日志是 MySQL 提供的一种日志记录,它用来记录在 MySQL 中响应时间超过阀值的语句,阈值指的是运行时间超过 long_query_time 值的 SQL,则会被记录到慢查询日志中。long_query_time 的默认值为 10,意思是运行 10秒 以上的语句。默认情况下,MySQL 数据库并不启动慢查询日志,需要我们手动来设置这个参数。 慢查询需要知道的 “点”  企业级开发中,慢查询日志是会打开的。但是这同样会带来一定的性能影响。   慢查询日志支持将日志记录写入文件,也支持将日志记录写入数据库表   默认的阈值(long_query_time)是 10,这个显然不可用,通常,对于用户级应用而言,我们将它设置为 0.2  慢查询相关的变量 查看变量的 SQL 语句
chenchenchen
2019/09/03
3K0
MySQL 慢查询、 索引、 事务隔离级别
关于MySQL事务隔离级别
MySQL遵循SQL:1992标准,提供READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ和SERIALIZABLE四种事务隔离级别。InnoDB默认使用的事务隔离级别是REPEATABLE READ。
星哥玩云
2022/08/16
7360
MySQL事务隔离级别和MVCC
MySQL是一个服务器/客户端架构的软件,对于同一个服务器来说,可以有若干个客户端与之连接,每个客户端与服务器连接上之后,就可以称之为一个会话(Session)。我们可以同时在不同的会话里输入各种语句,这些语句可以作为事务的一部分进行处理。不同的会话可以同时发送请求,也就是说服务器可能同时在处理多个事务,这样子就会导致不同的事务可能同时访问到相同的记录。我们前边说过事务有一个特性称之为隔离性,理论上在某个事务对某个数据进行访问时,其他事务应该进行排队,当该事务提交之后,其他事务才可以继续访问这个数据。但是这样子的话对性能影响太大,所以设计数据库的大叔提出了各种隔离级别,来最大限度的提升系统并发处理事务的能力,但是这也是以牺牲一定的隔离性来达到的。
南风
2019/09/17
6610
MySQL事务隔离级别和MVCC
MySQL 事务隔离级别[通俗易懂]
通过如下 SQL 可以查看数据库实例默认的全局隔离级别和当前 session 的隔离级别:
全栈程序员站长
2022/11/04
3K0
循序渐进 MySQL 事务隔离级别
事务简言之就是一组 SQL 执行要么全部成功,要么全部失败。MYSQL 的事务在存储引擎层实现。
波罗学
2019/07/31
5040
五分钟后,你将真正理解MySQL事务隔离级别!
事务是一组原子性的SQL操作,所有操作必须全部成功完成,如果其中有任何一个操作因为崩溃或其他原因无法执行,那么所有的操作都不会被执行。也就是说,事务内的操作,要么全部执行成功,要么全部执行失败。
Bug开发工程师
2020/02/19
5080
五分钟后,你将真正理解MySQL事务隔离级别!
事务隔离级别
  MySQL是一个 客户端/服务器 架构的软件,对于同一个服务器来说,可以有若干个客户端与之连接,每 个客户端与服务器连接上之后,就可以称为一个会话( Session )。每个客户端都可以在自己的会话中 向服务器发出请求语句,一个请求语句可能是某个事务的一部分,也就是对于服务器来说可能同时处理 多个事务。事务有 隔离性 的特性,理论上在某个事务 对某个数据进行访问 时,其他事务应该进行 排 队 ,当该事务提交之后,其他事务才可以继续访问这个数据。但是这样对 性能影响太大 ,我们既想保持 事务的隔离性,又想让服务器在处理访问同一数据的多个事务时 性能尽量高些 ,那就看二者如何权衡取 舍了。
一个风轻云淡
2022/11/15
8510
事务隔离级别
mysql事务隔离级别详解和实战
设置innodb的事务级别方法是:set 作用域 transaction isolation level 事务隔离级别,例如~
sunsky
2020/08/20
9030
mysql事务隔离级别详解和实战
事务隔离级别总结
事务(Transaction)是数据库系统中一系列操作的一个逻辑单元,所有操作要么全部成功要么全部失
张申傲
2020/09/03
7100
相关推荐
数据库事务隔离级别
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档