什么是数据并发 *** 作呢?
就是同一时间内,不同的线程同时对一条数据进行读写 *** 作。
在互联网时代,一个系统常常有很多人在使用,因此就可能出现高并发的现象,也就是不同的用户同时对一条数据进行 *** 作,如果没有有效的处理,自然就会出现数据的异常。而最常见的一种数据并发的场景就是电商中的秒杀,成千上万个用户对在极端的时间内,抢购一个商品。针对这种场景,商品的库存就是一个需要控制的数据,而多个用户对在同一时间对库存进行重写,一个不小心就可能出现超卖的情况。
针对这种情况,我们如何有效的处理数据并发呢?
第一种方案、数据库锁
从锁的基本属性来说,可以分为两种:一种是共享锁(S),一种是排它锁(X)。在MySQL的数据库中,是有四种隔离级别的,会在读写的时候,自动的使用这两种锁,防止数据出现混乱。
这四种隔离级别分别是:
读未提交(Read Uncommitted)
读提交(Read Committed)
可重复读(Repeated Read)
串行化(Serializable)
当然,不同的隔离级别,效率也是不同的,对于数据的一致性保证也就有不同的结果。而这些可能出现的又有哪些呢?
脏读(dirty read)
当事务与事务之间没有任何隔离的时候,就可能会出现脏读。例如:商家想看看所有的订单有哪些,这时,用户A提交了一个订单,但事务还没提交,商家却看到了这个订单。而这时就会出现一种问题,当商家去 *** 作这个订单时,可能用户A的订单由于部分问题,导致数据回滚,事务没有提交,这时商家的 *** 作就会失去目标。
不可重复读(unrepeatable read)
一个事务中,两次读 *** 作出来的同一条数据值不同,就是不可重复读。
例如:我们有一个事务A,需要去查询一下商品库存,然后做扣减,这时,事务B *** 作了这个商品,扣减了一部分库存,当事务A再次去查询商品库存的时候,发现这一次的结果和上次不同了,这就是不可重复读。
幻读(phantom problem)
一个事务中,两次读 *** 作出来的结果集不同,就是幻读。
例如:一个事务A,去查询现在已经支付的订单有哪些,得到了一个结果集。这时,事务B新提交了一个订单,当事务A再次去查询时,就会出现,两次得到的结果集不同的情况,也就是幻读了。
那针对这些结果,不同的隔离级别可以干什么呢?
“读未提(Read Uncommitted)”能预防啥?啥都预防不了。
“读提交(Read Committed)”能预防啥?使用“快照读(Snapshot Read)”方式,避免“脏读”,但是可能出现“不可重复读”和“幻读”。
“可重复读(Repeated Red)”能预防啥?使用“快照读(Snapshot Read)”方式,锁住被读取记录,避免出现“脏读”、“不可重复读”,但是可能出现“幻读”。
“串行化(Serializable)”能预防啥?有效避免“脏读”、“不可重复读”、“幻读”,不过运行效率奇差。
好了,锁说完了,但是,我们的数据库锁,并不能有效的解决并发的问题,只是尽可能保证数据的一致性,当并发量特别大时,数据库还是容易扛不住。那解决数据并发的另一个手段就是,尽可能的提高处理的速度。
因为数据的IO要提升难度比较大,那么通过其他的方式,对数据进行处理,减少数据库的IO,就是提高并发能力的有效手段了。
最有效的一种方式就是:缓存
想要减少并发出现的概率,那么读写的效率越高,读写的执行时间越短,自然数据并发的可能性就变小了,并发性能也有提高了。
还是用刚才的秒杀举例,我们为的就是保证库存的数据不出错,卖出一个商品,减一个库存,那么,我们就可以将库存放在内存中进行处理。这样,就能够保证库存有序的及时扣减,并且不出现问题。这样,我们的数据库的写 *** 作也变少了,执行效率也就大大提高了。
当然,常用的分布式缓存方式有:Redis和Memcache,Redis可以持久化到硬盘,而Memcache不行,应该怎么选择,就看具体的使用场景了。
当然,缓存毕竟使用的范围有限,很多的数据我们还是必须持久化到硬盘中,那我们就需要提高数据库的IO能力,这样避免一个线程执行时间太长,造成线程的阻塞。
那么,读写分离就是另一种有效的方式了
当我们的写成为了瓶颈的时候,读写分离就是一种可以选择的方式了。
我们的读库就只需要执行读,写库就只需要执行写,把读的压力从主库中分离出去,让主库的资源只是用来保证写的效率,从而提高写 *** 作的性能。
主要通过架构设计来减少高并发对数据库的压力;比如 在数据库和应用程序之间,增加 DAL层,通过代理,连接池等,保证数据库与业务程序由一定的缓冲和关系梳理;
在数据库前面,加一个缓存层,让大部分数据访问,都直接在缓存层获取数据,不用访问到后端的MySQL数据库;
1. 事实上,交易不帮你在这里多...除非你想有运行在多个HTTP请求(你很可能不希望)的交易。 有什么用在这些情况下是“乐观锁定”。 Django的ORM不支持,据我所知。但一直以来关于添加此功能。 那么,你是你自己的。基本上,你应该做的就是添加一个“版本”字段,你的模型,并把它传递给一个隐藏字段。正常周期的更新是: 读取数据并显示给 用户可以修改数据 用户发布的数据 该应用程序将其保存回数据库。 乐观锁,当你保存数据,你检查,如果你得到了从后面的版本是作为一个在数据库中,然后更新数据库和版本。如果它们不是,那有一直以来被加载的数据的变化。 你可以做到这一点与像一个单一的SQL调用:UPDATE ... WHERE version = 'version_from_user'这个调用将更新数据库只有在版本仍然是
2. 我就是这样做的Django的乐观锁:updated = Entry.objects.filter(Q(id=e.id) &&Q(version=e.version))\
.update(updated_field=new_value, version=e.version+1)
if not updated:
raise ConcurrentModificationException()
上面列出的代码可以在自定义管理。 我提出以下假设: 筛选()。update()方法会导致在一个单一的数据库查询过滤器是懒惰 数据库查询是原子 这些假设都足以确保没有其他人之前已经更新了条目。如果有多个行被更新这样你的交易。 警告Django的文件: 请注意,update()方法是 直接转换为SQL 这是一个批量 *** 作 直接更新。它不运行任何 保存(您的模型)的方法,或发出 该pre_save或post_save信号
3. 这个问题是有点老了,我的回答有点晚,但经过我的理解使用这个已被固定在Django 1.4:select_for_update(nowait=True)
看到文档 返回一个QuerySet,将锁定行,直到事务结束,产生一个SELECT ...有关支持的数据库UPDATE的SQL。 通常情况下,如果另一个事务已获得所选择的行上的锁,则查询将阻塞,直到锁被释放。如果这不是你想要的行为,请致电select_for_update(NOWAIT=TRUE)。这将使调用非阻塞的。如果已经获取了冲突的锁被另一个事务时的QuerySet进行评估,DatabaseError的将得到提升。 当然,这只会工作,如果后端支持的“选择更新”功能,这对于例如SQLite不。不幸的是:nowait=True不支持MySql的,有你有nowait=False,这只会阻塞,直到锁被释放。
4. 对于未来的参考,退房离开的时候(在浏览器中,例如崩溃)的页面,并锁定它锁定的方式,不留下永恒的锁,通过javascript的解锁的混合物。下
5. 你应该Django的交易中间件,至少,甚至不管这个问题。 至于你实际有编辑数据的问题...是的,使用锁。或: 检查什么版本正在更新对(这样做牢固,不能简单地破解系统说,他们正在更新的最新副本!),且仅当该版本是最新的更新。否则,返回一个新页面与原来的版本,他们编辑,他们提交的版本,和别人写的新版本(S)。问他们变成一体,完全取决于最新的版本。你可以尝试类似的diff +补丁工具集,但你需要有方法工作失败的案例,无论如何,所以开始了。此外,您将需要保存的版本历史记录,并允许管理员恢复的变化,在无意的情况下或向上,但你应该有反正。 有很可能是Django应用程序/库,做这个最适合你。
6. 为了安全起见,数据库需要支持事务。 如果字段是“自由形式”如文字等等,你需要允许可以编辑的字段(你不能有所有权的数据),你可以存储在变量中的原始数据。 当committs,检查输入数据从原始数据更改(如果不是,你不需要通过重写旧数据打扰DB) 如果原来在数据库中的当前数据是可以保存,如果它改变了你可以示区别,并问该怎么办。 如果字段是一个数字如账户余额,在商店等项目的数量,你可以自动处理它,如果你计算出原始值(存储开始时填写表单)和新的价值,你就可以开始一个事务读取当前值之间的差额新增的差别,然后结束交易。如果你不能有负值,则应该中止交易,如果结果为负,并告诉 我不知道Django的,所以我不能给你德cod3s .. )
7. 另一个需要注意的是这个词“原子”.a个原子,你的数据库的更改要么发生或无法快速搜索说明这个问题问Django中的原子 *** 作。
8. 上面的想法updated = Entry.objects.filter(Q(id=e.id) &&Q(version=e.version))\
.update(updated_field=new_value, version=e.version+1)
if not updated:
raise ConcurrentModificationException()
看起来不错,应该能正常运行,即使没有序列化的交易。 问题是如何将deafult。保存()的行为,以不必须做人工管道来调用。update()方法。 我看着自定义管理想法。 我的计划是覆盖被称为Model.save_base()来执行更新的经理。 这是在Django 1.3当前代码def _update(self, values, **kwargs):
return self.get_query_set()._update(values, **kwargs)
什么需要恕我直言做的是这样的:def _update(self, values, **kwargs):
#TODO Get version field value
v = self.get_version_field_value(values[0])
return self.get_query_set().filter(Q(version=v))._update(values, **kwargs)
类似的事情需要发生的删除。但是删除是有点难度的Django是相当巫术在这方面通过django.db.models.deletion.Collector。 这是奇怪的,像Django的modren工具缺乏对Optimictic Concurency控制指导。 当我解开这个谜,我会更新这个帖子。希望解决方案将是不涉及万吨编码,怪异的意见,跳绳重要部分的Django等的一个很好的Python的方式
9. 从这里开始: 我假设将举行一个隐藏字段中你试图挽救细节的表格。def save(self):
if(self.id):
foo = Foo.objects.get(pk=self.id)
if(foo.timestamp >self.timestamp):
raise Exception, "trying to save outdated Foo"
super(Foo, self).save()
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)