一个经常被忽略的关于更新异常的问题 http://www.cnblogs.com/leoo2sk/archive/2008/04/04/1137776.html
让我们一起来想象以下的一个场景:
我们正在开发一个B/S结构的CMS系统,数据库中有张表叫做"t_Article",用来保存文章信息。其中有三个字段:Article_ID、Article_Title、Article_Content,分别表示文章的ID、标题、正文,其中Article_ID为主键。OK,现在我们要为这个系统写文章修改功能,并且前台页面已经做好了,你会这么做?我想,你一定会首先根据GET过来的文章ID参数,获取相应文章的信息,也许你的SQL查询语句会是这样的:SELECT Article_Title,Article_Content FROM t_Article WHERE Article_ID='id'。下面,你会把获取到的标题和正文放入相应input和textarea标签的相应位置,以让它们显示在文本框中,供管理员修改。修改完后,你将会用Update语句更新数据库中的信息,例如:UPDATE t_Article SET Article_Title='title',Article_Content='content' WHERE Article_ID='id'。然后一切似乎很顺利,修改文章完全没有问题。接着,我们又投入别的功能的开发。
经过夜以继日的奋战,终于有一天系统交付使用了,我们开发小组得到了丰厚的酬金和两个星期的休假。
某一天,客户公司的网站管理员用这个CMS系统发布了一条信息,标题是"会议通知",正文为"本周三下午3点在公司主会议室开会"。可是,后来情况发生了改变,会议提前到两点进行,BOSS将这个信息告诉了管理员Alice,让他更新一下网站上的通知。于是Alice进入了后台管理,并进入了文章更新界面,就在这时,另一个管理员Bob也进入了文章更新页面,因为他觉得"本周三"这样的字眼不够明确,他想在后面添加上确切的日期会更好。大家请注意,关键问题来了!在这时,Alice还没有更新文章,所以,Bob读取的信息仍是原来的正文:本周三下午3点在公司主会议室开会。当Bob正在文本框里更改文章时,Alice已经完成了更新,她将正文改为"本周三下午两点在公司主会议室开会",并点击了更新按钮,于是数据库被更新了,可不幸的是,Bob这边文本框的信息可没有更新,还是先前读取的旧数据,当然,他也不知道Alice更新了数据库。于是,他乐呵呵的将正文改为"本周三(6月16日)下午3点在公司主会议室开会",然后同样点击了更新按钮。
我想大家已经猜出后来发生了什么,客户公司员工集体会议迟到,更遭的是,客户找到了我们的BOSS,于是我们开发小组被扣除了三个月的奖金。
为什么会发生如此悲惨的局面?我想大家已经很明白了,这种大家常用的更新数据开发方式,会导致数据更新异常问题。那么应该如何解决这个问题呢?也许你会说可以加锁,但是这相当麻烦,而且不易实现。下面,我提出一种简单的解决方案。
在上面的示例中,之所以会出现更新异常,关键是管理员在更新前,不能确定从他读取数据到点击更新按钮这段时间有没有人动过这些数据,如果在提交更新时,系统能判断出这段时间是否进行了更新,并且在有人更新过的情况下抛出一个异常而拒绝这次更新,并强制重新读取数据,问题就得到解决。这就是我的思路,而具体实现方法是,在每次读取信息后,将所有信息保存起来,在更新时,通过WHERE语句使每个字段都与原始数据进行匹配,如果数据被人动过,这条Update语句必然会失败。例如,我们可以将上文提到的Update语句改为下面的方式:(假设原始的标题和正文保存在oldTitle和oldContent中)UPDATE t_Article SET Article_Title='title',Article_Content='content' WHERE Article_ID='id' AND Article_title='oldTitle' AND Article_content='oldContent'。现在我们来想想,如果在这条语句执行前,数据已经被改动过,那么WHERE后面的语句指向的记录是不存在的,更新操作自然无法执行,这是一般会抛出一个异常。我们截获这个异常,然后做适当处理,例如提示更新不能进行,然后重新从数据库读出新的数据到页面文本框中。
到这里,问题已经得到解决了。当然,这个解决方案也会带来一定问题,例如Bob化了两个小时修改一篇文章,但不幸的是在他全神贯注修改文章时,这篇文章被Alice改动了,那么当倒霉的Bob点击更新按钮时,他将要接受刚才两个小时的汗水付之东流的残忍事实,但是,这总比全体会议迟到好吧……当然,我们可以更完美一些,例如通过Ajax技术将当前修改保存起来,以避免不幸发生。这已经超出本文讨论范围了。
FeedBack:
我们正在开发一个B/S结构的CMS系统,数据库中有张表叫做"t_Article",用来保存文章信息。其中有三个字段:Article_ID、Article_Title、Article_Content,分别表示文章的ID、标题、正文,其中Article_ID为主键。OK,现在我们要为这个系统写文章修改功能,并且前台页面已经做好了,你会这么做?我想,你一定会首先根据GET过来的文章ID参数,获取相应文章的信息,也许你的SQL查询语句会是这样的:SELECT Article_Title,Article_Content FROM t_Article WHERE Article_ID='id'。下面,你会把获取到的标题和正文放入相应input和textarea标签的相应位置,以让它们显示在文本框中,供管理员修改。修改完后,你将会用Update语句更新数据库中的信息,例如:UPDATE t_Article SET Article_Title='title',Article_Content='content' WHERE Article_ID='id'。然后一切似乎很顺利,修改文章完全没有问题。接着,我们又投入别的功能的开发。
经过夜以继日的奋战,终于有一天系统交付使用了,我们开发小组得到了丰厚的酬金和两个星期的休假。
某一天,客户公司的网站管理员用这个CMS系统发布了一条信息,标题是"会议通知",正文为"本周三下午3点在公司主会议室开会"。可是,后来情况发生了改变,会议提前到两点进行,BOSS将这个信息告诉了管理员Alice,让他更新一下网站上的通知。于是Alice进入了后台管理,并进入了文章更新界面,就在这时,另一个管理员Bob也进入了文章更新页面,因为他觉得"本周三"这样的字眼不够明确,他想在后面添加上确切的日期会更好。大家请注意,关键问题来了!在这时,Alice还没有更新文章,所以,Bob读取的信息仍是原来的正文:本周三下午3点在公司主会议室开会。当Bob正在文本框里更改文章时,Alice已经完成了更新,她将正文改为"本周三下午两点在公司主会议室开会",并点击了更新按钮,于是数据库被更新了,可不幸的是,Bob这边文本框的信息可没有更新,还是先前读取的旧数据,当然,他也不知道Alice更新了数据库。于是,他乐呵呵的将正文改为"本周三(6月16日)下午3点在公司主会议室开会",然后同样点击了更新按钮。
我想大家已经猜出后来发生了什么,客户公司员工集体会议迟到,更遭的是,客户找到了我们的BOSS,于是我们开发小组被扣除了三个月的奖金。
为什么会发生如此悲惨的局面?我想大家已经很明白了,这种大家常用的更新数据开发方式,会导致数据更新异常问题。那么应该如何解决这个问题呢?也许你会说可以加锁,但是这相当麻烦,而且不易实现。下面,我提出一种简单的解决方案。
在上面的示例中,之所以会出现更新异常,关键是管理员在更新前,不能确定从他读取数据到点击更新按钮这段时间有没有人动过这些数据,如果在提交更新时,系统能判断出这段时间是否进行了更新,并且在有人更新过的情况下抛出一个异常而拒绝这次更新,并强制重新读取数据,问题就得到解决。这就是我的思路,而具体实现方法是,在每次读取信息后,将所有信息保存起来,在更新时,通过WHERE语句使每个字段都与原始数据进行匹配,如果数据被人动过,这条Update语句必然会失败。例如,我们可以将上文提到的Update语句改为下面的方式:(假设原始的标题和正文保存在oldTitle和oldContent中)UPDATE t_Article SET Article_Title='title',Article_Content='content' WHERE Article_ID='id' AND Article_title='oldTitle' AND Article_content='oldContent'。现在我们来想想,如果在这条语句执行前,数据已经被改动过,那么WHERE后面的语句指向的记录是不存在的,更新操作自然无法执行,这是一般会抛出一个异常。我们截获这个异常,然后做适当处理,例如提示更新不能进行,然后重新从数据库读出新的数据到页面文本框中。
到这里,问题已经得到解决了。当然,这个解决方案也会带来一定问题,例如Bob化了两个小时修改一篇文章,但不幸的是在他全神贯注修改文章时,这篇文章被Alice改动了,那么当倒霉的Bob点击更新按钮时,他将要接受刚才两个小时的汗水付之东流的残忍事实,但是,这总比全体会议迟到好吧……当然,我们可以更完美一些,例如通过Ajax技术将当前修改保存起来,以避免不幸发生。这已经超出本文讨论范围了。
FeedBack:
--引用--------------------------------------------------
T2噬菌体: --引用--------------------------------------------------
seamusic: 这样不如在数据库里加个字段,记录内容和标题的MD5值。。。。。
再说,加个行锁一点都不麻烦……
--------------------------------------------------------
呵呵,这也是一种解决办法。但是并不是所有的数据库系统都支持行锁……而且加行锁的最大缺点是,如果有人在修改文章,其他人就没法浏览文章,导致效率底下。至于记录MD5确实也是一种可行的解决方案,这样的话,就附加一个字段,每次插入或更新数据,将所有字段连接成一个长字符串,生成MD5值放在附加字段中,更新前再比较。
T2噬菌体: --引用--------------------------------------------------
seamusic: 这样不如在数据库里加个字段,记录内容和标题的MD5值。。。。。
再说,加个行锁一点都不麻烦……
--------------------------------------------------------
呵呵,这也是一种解决办法。但是并不是所有的数据库系统都支持行锁……而且加行锁的最大缺点是,如果有人在修改文章,其他人就没法浏览文章,导致效率底下。至于记录MD5确实也是一种可行的解决方案,这样的话,就附加一个字段,每次插入或更新数据,将所有字段连接成一个长字符串,生成MD5值放在附加字段中,更新前再比较。
posted on 2008-06-21 23:34 malaikuangren 阅读(329) 评论(0) 编辑 收藏 举报