当前位置:首页 > 问答 > 正文

试着用sql update改动好几个表,看看会不会出啥问题和奇怪情况

直接改动多个表,听起来就像是一个人想同时用两只手写两篇不同的作文,还得保证都不出错,这事儿听起来就不太靠谱,在实际的数据库操作里,更是充满了各种意想不到的坑,这些问题可不是理论上的,很多都是程序员们在真实的项目里用教训换来的。

最直接也最常见的问题就是,更新操作中途失败,导致数据“一半新一半旧”,想象一下,你有一个电商系统,用户下了一个订单,这个订单信息存在“订单表”里,同时需要减少“商品库存表”里对应商品的库存数量,你的想法是完美的:先减少库存,然后更新订单状态为“已确认”,你用两条UPDATE语句,一条改库存表,一条改订单表,但如果老天爷跟你开了个玩笑,就在你成功减少了库存之后,更新订单表之前,数据库服务器突然断电或者网络断开了,这下可好,库存已经扣掉了,但订单却没有成功确认,用户那边看到订单没成功,可能会再次下单,结果库存又被扣了一次,或者更糟,这个商品本来只剩一件,被第一个用户“幽灵”扣减了,导致第二个真正想买的用户无法下单,整个系统的数据就乱套了,对不上账了,这就是典型的数据不一致问题,根据许多数据库开发的经验分享,这类问题是分布式系统和复杂业务逻辑中最头疼的问题之一。

试着用sql update改动好几个表,看看会不会出啥问题和奇怪情况

另一个奇怪的情况是,你可能会不小心改动了压根没想改的数据,SQL的UPDATE语句有个特点,它会作用于所有满足WHERE条件的行,如果你在写UPDATE语句时,WHERE条件写得不严谨,或者多个表之间有复杂的关联关系,你就可能“误伤”,你想更新“部门A”的所有员工,把他们的状态改为“活跃”,但你写的语句可能因为表连接(JOIN)的方式不对,结果把曾经在部门A待过、但现在已调到部门B的员工也给更新了,这种错误非常隐蔽,可能过了很久对账的时候才发现数据对不上,有经验的数据库管理员会强调,在执行任何UPDATE操作前,务必先把它写成SELECT语句,确认一下到底会影响到哪些数据,这是一个非常重要的安全习惯。

试着用sql update改动好几个表,看看会不会出啥问题和奇怪情况

还有一种情况是触发器带来的“惊喜”,有些数据库表上定义了触发器,触发器的意思是,当你对这张表做增删改操作时,会自动触发执行另一段预设好的SQL代码,这本意是为了保证数据完整性或自动记录日志,但它也会让简单的UPDATE操作变得不可预测,你只是想简单地更新一下用户表的电话号码,但没想到这个表上有个触发器,会自动把这次更改记录到另一张“用户信息修改历史表”里,如果这张历史表因为磁盘空间不足而写入失败,那么连带你最初那个简单的更新电话号码的操作也会整体失败,你可能会觉得很困惑:“我只是改个号码,怎么会失败?” 这就是触发器在背后“捣鬼”,如果你不了解所有表上设置的触发器,更新操作就像在雷区里走路。

当多个用户或程序同时对相关的表进行更新时,会引发死锁这种最让人头疼的奇怪情况,死锁可以打个比方:两个人要过一扇窄门,A在门里侧拿着里侧的门把手,B在门外侧拿着外侧的门把手,两人都等着对方先松手让自己过去,结果谁都动不了,在数据库里,比如事务A先锁定了表1的某一行,准备去锁定表2的一行;而同时,事务B先锁定了表2的那一行,准备回头来锁定表1的那一行,两个事务都在互相等待对方释放锁,但又都不释放自己手里的锁,这就卡死了,数据库最终会检测到这种情况,并强制回滚(撤销)其中一个事务,让它失败,对于被回滚的用户来说,他会收到一个莫名其妙的错误,明明刚才操作还好好的,处理死锁需要仔细设计业务流程和操作顺序,避免这种循环等待的发生。

直接用SQL UPDATE语句去改动多个表,尤其是在没有事务保证原子性的情况下,或者在不完全了解表结构、关联关系和触发器设置的情况下,是一件风险很高的事情,它很容易导致数据不一致、误操作、不可预知的连锁反应以及死锁,成熟的软件开发中,通常会通过业务逻辑代码来协调对多个表的更改,并且一定会把相关的更新操作放在一个数据库事务中,确保要么全部成功,要么全部失败,就像什么都没发生过一样,这样才能最大程度地避免这些奇怪的问题和麻烦。