核桃博客
原创,本博客所有文章全部原创,转载本文
请注明来自核桃博客
,谢谢!
1
.问题场景
a. 用户A打开应用的界面,看到数据库的某条记录
b.用户B打开应用的界面,看到同样一条记录
c. 用户A对记录做了修改
d. 对于web应用而言[假设没有应用comet类似技术],通常B不知道这个修改,这时B也对同样这条记录做修改,那B就有可能覆盖A做的修改;
这个问题在数据库中被称为丢失更新
问题
2
.我自己对这个问题的理解过程是这样的:
a. 不知道这个问题
我在做开发好长时间之后才意识到这个问题,意识到这个问题之后,我后来发现很长一段时间内都没真正搞明白为什么这是个问题-_-
而且我发现现在周围的很多同事,尤其是新毕业的学生,其实也一直过了很长时间都没明白这个问题,这说明吧不知道这个丢失更新问题是一个非常普遍的问题:)
b.用信号量以及操作之前再次验证的方法解决
最开始的时候,测试发现了这样一个问题,要求解决,我把操作系统的教科书搬来,对照着写了一个信号量semaphore类[那时候还是jdk
1.4.2,jdk里面没有concurrent包],花了好长时间测试这个semaphore的实现是正确的[重复发明轮子的血泪史..],
然后用来控制这个操作,每次操作前获取信号量,然后验证,再做真正的数据库操作。。。相当于在应用层每次都只做一件事。
c. 再次理解
再后来,我看了Tom的这边9i和10g的书,书中提到前面的丢失更新过程,大概有点明白为什么这是个问题
但是其实我有个疑问,对于数据库中的记录而言,A做的修改本来就有可能被B覆盖的,为什么这会是一个丢失更新问题呢? 正好项目里面又出现了类似的情况,我仔细观察了下,终于明白为什么这是个问题,以及为什么要使用对应的乐观锁悲观锁方案了。下面对此做详细说明
3.
一个比较清楚的场景
下面这个假设的实际场景可以比较清楚的帮助我们理解这个问题:
-
假设当当网上用户下单买了本书,这时数据库中有条订单号为001的订单,其中有个status字段是’有效’,表示该订单是有效的;
-
后台管理人员查询到这条001的订单,并且看到状态是有效的
-
用户发现下单的时候下错了,于是撤销订单,假设运行这样一条SQL: update order_table set status = ‘取消’ where order_id = 001;
-
后台管理人员由于在b这步看到状态有效的,这时,虽然用户在c
这步已经撤销了订单,可是管理人员并未刷新界面,看到的订单状态还是有效的,于是点击”发货”按钮,将该订单发到物流部门,同时运行类似如下SQL,将订
单状态改成已发货:update order_table set status = ‘已发货’ where order_id = 001
如果当当的系统这样实现,显然不对了,肯定要挨骂了,明明已经取消了订单,为什么还会发货呢?而且确实取消订单的操作发生在发货操作之前啊。
因为在这样的实现下,后台管理人员无论怎么做都有可能会出错,因为他打开系统看到有效的订单和他点发货之间肯定有个时间差,在这个时间差的时候总是存在用
户取消订单的可能。
4.
当时的详细解决方法
几年前当测试人员告诉我系统存在这个问题的时候,我的解决方法是这样的, 首先,先把操作系统的教科书搬来,然后对照着了一个semaphore,然后反复测试各种情况证明写的是正确的; 然后,
1. 获取一个信号量,保证每次只能有一个线程进入下面的步骤
2. 检查数据库,看这条订单是否状态是有效的
a. 如果有效则继续,进入发货步骤 b) 如果无效则返回,释放信号量,告诉用户状态已经发生改变
3. 发货,释放信号量
看到这里,也许很多人要骂我蠢了,直接把SQL语句改成下面这样吧就可以了么? update order_table set status =
‘已发货’ where order_id = 001 and status = ‘有效’
是的,的确是这样。虽然我当时的项目的情况比和这个稍微复杂一点,涉及到多张表格,不能直接这么做,但当时的确不知道这个更新丢失问题,也没想到合适的类
似方式,于是就在应用层做了这么一个每次实际上只能有一个用户在做真正的更新这样一个方式来解决,这样做的结果是,在应用层单独做了类似这么一个锁的机
制。我记得当时的项目毕业答辩的时候,老师问我同步的这个问题不直接用数据库的锁的方案来解决?我当时胡乱回答了下,后来想起来,其实压根没理解老师的意
思-_-
而且这样做有一个问题,假设在特殊情况下,这条订单被DBA直接修改了,没有经过应用,那么应用做这个操作也会是错的,因为在2.a到3之前的这段时间,
有可能正好是DBA直接修改的时候。那么3做的操作也是不对的。
而且,现实情况是在后来的几年开发过程中,我也的确在一些不同的项目代码中看到,其他很多人也在使用类似的代码解决测试人员告诉他们的这些同步问题-_-
5
.正确而简洁的解决方法
问题清楚了,也说明了我曾经使用的解决方案也是一个简洁直接的解决方案,纯粹是把简单问题复杂化,下面说说实际有效的解决方案;
就这个丢失更新问题,可以通过数据库的锁来实现,基本两种思路,一种是悲观锁,另外一种是乐观锁;
简单的说就是一种假定这样的问题是高概率的,最好一开始就锁住,免得更新老是失败;另外一种假定这样的问题是小概率的,最后一步做更新的时候再锁住,免得
锁住时间太长影响其他人做有关操作;
6
.
乐观锁的方法
这里先说web开发中常用的乐观锁的方法:
1.很简单,就是使用前面所说的这样一条SQL,这其实是所谓使用”前镜像”的方式来保证需要更新的数据是符合要求的,
update order_table set status = ‘已发货’ where order_id = 001 and status
= ‘有效’ Tom的书上举的例子是对所有列做更新,所以他的SQL大致如下 Update table set col1 =
newcol1value, col2 = newcol2value…. where col1 = oldcol1value and col2 =
oldcol2value….
这个我觉得需要根据应用具体分析,如果需要判断所有的值,那就判断所有的值,如果只关心其中一个或部分值,那只需要取相关的值就好了,就比如这里的订单的
状态
2.使用版本列[比如时间戳]
这个方法比较简单,也最常用,就是在数据库表格中加一列last_modified_date,就是最后更新的时间,每次更新的时候都将这列设成systimestamp,当前系统时间;
然后每次更新的时候,就改成这样 Update table set col = newvalue where id = ** and
last_modified_date = old last_modified_date
这样,就可以检验出数据库的值是否在上次查看和这次更新的时候发生了变化,如果发生了变化,那么last_modified_date就变化了,以后的更
新就会返回更新了0行,系统就可以通知用户数据发生了变化,然后选择刷新数据或者其他流程。
至于这个last_modified_date的维护,可以选择让应用每次都维护这个值,或者是使用存储过程来包装更新的操作,或者是使用触发器来
更新相关的值。几种方法各有利弊,比如应用维护需要保证每段相关代码都正确的维护了这个值;存储过程有一定的开销,通常很多开发对写存储过程可能也不熟
练;触发器是简单的实现,但是也是有开销的。具体使用哪种方法需要根据实际情况具体取舍。
3.使用校验或Hash值
这种方法和前面的方法类似,无非是根据其他有实际意义的列来计算出一个虚拟的列,我个人觉得TOM在介绍这个纯粹是介绍了一种”奇技淫巧”,反正我是在实际过程中不知道哪里会需要这样的解决方案,或许也是因为我知道的太少了吧:)
4.使用Oracle 10g的ORA_ROWSCN
这个就是利用10g的一个ora_rowscn特性,可以对每行做精确追踪,不过这个要求在create
table的时候就指定相关参数,表格如果创建了以后就不能用alter table来修改了,因为这依赖于物理的实际存储。
同样,我觉得这也可以归为”奇技淫巧”一类; 具体如果有兴趣了解详情的话,可以参考Tom的书
下次核桃博客
将介绍悲观锁来解决更新丢失问题
分享到:
相关推荐
本篇文章将详细解析Delphi通过ADO方式实现数据库连接的源码测试过程。 首先,我们来理解一下ADO的核心组件。在Delphi中,主要使用TADOConnection、TADOCommand、TADODataset等组件来实现数据库操作。TADOConnection...
在“jdbctest”这个项目中,我们可以预期包含一个或多个Java源文件,这些文件演示了上述步骤,用于测试数据库连接是否成功。通常,这些测试会包含一个简单的SELECT语句,比如查询数据库中的表信息,以确保连接有效。...
winform 检测端口 测试数据库连接 80端口 IIS 端口 ASP.NET Oracle winform 检测端口 测试数据库连接 80端口 IIS 端口 ASP.NET Oracle winform 检测端口 测试数据库连接 80端口 IIS 端口 ASP.NET Oracle
总的来说,这篇文章提供了使用JMeter进行GBase数据库压力测试的详细步骤,涵盖了环境配置、测试计划创建、执行以及结果分析的全过程。这对于需要评估和优化GBase数据库性能的IT专业人员来说是一份非常实用的指南。
三是在多种测试案例中表现稳定,不受特定输入数据的影响。这些特性使得OMLA成为解决分布式数据库系统中数据碎片分配问题的有效工具。 #### 结论 综上所述,数据碎片分配问题是设计高效分布式数据库系统的关键。本文...
文章中还提到了分布式关系型数据库质量特性的评价,指出除了功能、性能、安全等质量属性外,对于数据库这样的基础软件产品,还需要结合软件本身的技术特点,并在质量特性的框架下进行设计和细化测试指标。...
### 分布式数据库文章精选知识点概述 #### 一、金融行业分布式数据库架构改造及产品选型难点解读 - **背景**: 介绍了金融行业为何需要进行分布式数据库架构改造。 - **难点**: 包括数据一致性、安全性、性能优化等...
"新网新闻&文章发布系统数据库版"是一个专为新闻发布和内容管理设计的应用程序,它允许用户方便地创建、编辑和发布新闻与文章,并且依赖于数据库来存储和管理这些内容。这个系统的特色在于它的数据库驱动特性,确保...
本篇文章将深入探讨如何利用Laravel的一个组件来为数据库自动生成测试数据,以此提升测试效率和代码覆盖率。 标题提及的“基于Laravel的组件给数据库自动生成测试数据”,这通常指的是使用特定的扩展或包来快速生成...
标题和描述均聚焦于“用JMeter测量性能测试您的DB2数据库”,这表明文章的核心议题是探讨如何利用Apache JMeter这一开源工具,对IBM DB2数据库进行性能测试,以评估和优化其处理能力和响应时间。以下是对这一主题的...
- JMeter支持多种测试类型,包括HTTP、FTP、SOAP/REST、JDBC等,所以它可以用来测试数据库连接的性能。 2. **数据库连接池**: - 数据库连接池是一种管理数据库连接的技术,它在应用启动时预创建并维护一定数量的...
本篇文章将深入探讨sysbench的两个版本——sysbench0.5和sysbench1.1,并详细阐述它们对Oracle及达梦数据库的支持。 首先,sysbench0.5是针对早期版本的sysbench,它主要专注于MySQL数据库的性能测试,但也提供了对...
这篇文章将详细介绍如何利用DBUnit和H2内存数据库来构建单元测试环境。 首先,DBUnit是一个开源的Java库,专门用于在JDBC兼容的数据库上执行数据驱动的测试。它提供了对数据导入、导出以及数据验证的强大支持,使得...
* 迁移至天人文章管理系统后请先测试数据完整、统一之后再正式上线使用 * 会员数据迁移后使用会员的email的账号(@之前的字符,不足5位前面补数字0,例如pp@163.com,则000pp是密码,又例如petty@163,则petty是...
【数据库设计】文章中使用了SQL Server 2000创建了一个名为DAQ的数据库,其中包含一个Test表,用于存储测试数据。表结构包括时间戳(Time)、设备(device)、通道(channel)、采样率(sample rate)以及多个信号值...
Android 开发 MyEclipse 下测试连接 MySQL ...通过这篇文章,我们学会了如何在 MyEclipse 中连接 MySQL 数据库,并读取数据库中的数据。这种技术可以应用于各种 Android 项目中,例如新闻客户端、社交媒体 App 等。
- 完成以上配置后,可以尝试在订阅服务器上查看是否能接收到最新的数据更改情况,以此来测试数据库同步功能的有效性。 #### 六、结论 通过上述步骤,我们可以在 WinCC 自带的 SQL Server 数据库中实现数据库同步...
这个“基于SSM的文章管理系统(含数据库文件).zip”项目,是一个适合计算机科学、软件工程以及通信工程专业学生进行课程设计或毕业设计的实践案例。在这个项目中,开发者运用了SSM框架来实现一个文章管理的后端系统...