12.2.4 锁等待与死锁
在某些情况下由于占用的资源不能及时释放,而造成锁等待,也可叫锁冲突。锁等待会严重地影响数据库性能和日常工作。例如,当一个会话修改表A的记录时,它会对该记录加锁,而此时如果另一个会话也来修改此记录,那么第二个会话将因得不到排他锁而一直等待,此时会出现执行SQL时数据库长时间没有响应的现象。直到第一个会话把事务提交,释放锁,第二个会话才能对数据进行操作。
【示例3】锁等待
该示例将演示锁等待的现象,具体分为如下两个步骤。
1)打开SQL*Plus窗口,修改PRODUCTINFO表中PRODUCTID字段为0240090001的记录。脚本如下:
UPDATE PRODUCTINFO SET ORIGIN='修改1'WHERE PRODUCTID=0240090001;
若执行成功则提示:
已更新1行
此时虽然提示已更新,但事务并没有提交。接下来进行第二步操作。
2)打开另一个SQL*Plus窗口,同样修改PRODUCTINFO表中PRODUCTID字段为0240090001的记录。脚本如下:
UPDATE PRODUCTINFO SET ORIGIN='修改2'WHERE PRODUCTID=0240090001;
此时的执行效果不会提示已更新,而是一直等待。效果见图12.7。
图 12.7 等待更新数据
此时的情况是因为第一个会话封锁了该记录,但事务没有结束,锁不会释放,而这时第二个会话也要修改同一条记录,但它却没有办法获得锁,所以只能等待。如果第一个会话修改数据的事务结束,那么第二个会话就会结束等待。及时地结束事务是解决锁等待情况发生的有效方法。
死锁的发生和锁等待不同,它是锁等待的一个特例,通常发生在两个或多个会话之间。假设一个会话想要修改两个资源对象,可以是表也可以是字段,修改这两个资源的操作在一个事务当中。当它修改第一个对象时需要对其锁定,然后等待第二个对象,这时如果另外一个会话也需要修改这两个资源对象,并且已经获得并锁定了第二个对象,那么就会出现死锁,因为当前会话锁定了第一个对象等待第二个对象,而另一个会话锁定了第二个对象等待第一个对象。这样,两个会话都不能得到想要得到的对象,于是出现死锁。
【示例4】死锁的发生
下面是演示死锁发生的示例。具体分为如下4个步骤:
1)打开第一个SQL*Plus窗口,创建第一个会话,执行如下脚本,修改PRODUCTINFO表中PRODUCTID字段为0240090001的记录。脚本如下:
UPDATE PRODUCTINFO SET ORIGIN='修改'WHERE PRODUCTID=0240090001;
2)打开第二个SQL*Plus窗口,创建第二个会话,执行如下脚本,修改PRODUCTINFO表中PRODUCTID字段为0240090002的记录。脚本如下:
UPDATE PRODUCTINFO SET ORIGIN='修改'WHERE PRODUCTID=0240090002;
到目前为止,第一个会话锁定了PRODUCTID字段为0240090001的记录,第二个会话锁定了PRODUCTID字段为0240090002的记录。
3)第一个会话修改第二个会话已经修改的记录。执行脚本如下:
UPDATE PRODUCTINFO SET ORIGIN='修改'WHERE PRODUCTID=0240090002;
此时第一个会话将出现锁等待,因为它修改的对象已经被第二个会话锁定。效果如图12.8所示。
图 12.8 第一个会话出现锁等待
4)第二个会话修改第一个会话已经修改的记录。执行脚本如下:
UPDATE PRODUCTINFO SET ORIGIN='修改'WHERE PRODUCTID=0240090001;
此时会出现死锁的情况。Oracle会自动检测死锁的情况,并释放一个冲突锁,并把消息传递给对方事务。此时在第一个会话窗口中会提示检测到死锁,如图12.9所示。
图 12.9 死锁提示
此时Oracle自动做出处理,并重新回到锁等待的情况。出现锁等待的情况时应尽快地找出错误原因并对其处理,避免影响数据库性能。实际开发中出现此类情况大致有以下几种原因:
1)用户没有良好的编程习惯,偶尔会忘记提交事务,导致长时间占用资源。
2)操作的记录过多,而且操作过程中没有很好地对其分组。前面介绍过,对于数据量很大的操作,可以将其分成几组提交事务,这样可以避免长时间地占用资源。
3)逻辑错误,两个会话都想得到已占有的资源。
如果操作的数据对象长时间地没有响应,应该考虑锁阻塞的可能。此时可以利用OEM管理器查看。OEM的使用方式后面会有专门的介绍,这里不做过多讲解。
【示例5】使用企业管理器OEM终止锁阻塞情况
该示例可以分为如下3个步骤来查看锁阻塞情况并终止锁阻塞:
1)登录OEM管理器,在浏览器中输入https://Oracle服务器IP:1158/em,然后会提示有认证安装,单击安装即可。输入用户名/密码,单击【登录】按钮,进入图12.10所示的页面。此时可以在该页面的预警列表中发现有阻塞会话统计,见下面的标记。
图 12.10 登录进入OEM首页面
2)进入实例锁页面。在图12.10所示的页面中,单击【性能】选项,进入图12.11所示的页面。
图 12.11 【性能】标签页面
在该页面的“其他监视链接”中选择“实例锁”,进入实例锁的操作页面,如图12.12所示。
图 12.12 实例锁操作页面
3)终止阻塞锁。从图12.12框出部分可以看出会话ID是131的会话持有一个行级的排他模式的锁,而会话ID是141的会话则在请求同样的锁,所以出现了阻塞的情况。此时开发人员可以选择终止会话ID是131的会话来解决这个问题。选择会话ID是131的会话,单击【终止会话】按钮。在弹出的窗口使用默认选项,单击【是】按钮就完成了会话ID是131会话的终止,而此时的锁等待现象也解决了。