死锁的产生和解决

在联机事务处理(OLTP)的数据库应用系统中,多用户、多任务的并发性是系统最重要的技术指标之一。为了提高并发性,目前大部分RDBMS都采用加锁技术。然而由于现实环境的复杂性,使用加锁技术又不可避免地产生了死锁问题。因此如何合理有效地使用加锁技术,最小化死锁是开发联机事务处理系统的关键。          
死锁产生的原因           
     在联机事务处理系统中,造成锁死主要有两方面原因。一方面,由于多用户、多任务的并发性和事务的完整性要求,当多个事务处理对多个资源同时访问时,若双方已锁定一部分资源但也都需要对方已锁定的资源时,无法在有限的时间内完全获得所需的资源,就会处于无限的等待状态,从而造成其对资源需求的死锁。另一方面,数据库本身加锁机制的实现方法不同,各数据库系统也会产生其特殊的死锁情况。如在Sybase SQL Server 11中,最小锁为2K一页的加锁方法,而非行级锁。如果某张表的记录数少且记录的长度较短(即记录密度高,如应用系统中的系统配置表或系统参数表就属于此类表),被访问的频率高,就容易在该页上产生死锁。
         
容易发生死锁的几种情况如下:           
1>不同的存储过程、触发器、动态SQL语句段按照不同的顺序同时访问多张表;              
2>在交换期间添加记录频繁的表,但在该表上使用了非群集索引(non-clustered);              
3>表中的记录少,且单条记录较短,被访问的频率较高;          
4>整张表被访问的频率高(如代码对照表的查询等)。           

以上死锁情况的对应处理方法如下:
       
1>在系统实现时应规定所有存储过程、触发器、动态SQL语句段中,对多张表的操作总是使用同一顺序。如:有两个存储过程proc1、proc2,都需要访问三张表zltab、z2tab和z3tab,如果proc1按照zltab、z2tab和z3tab的顺序进行访问,那么,proc2也应该按照以上顺序访问这三张表。          
2>对在交换期间添加记录频繁的表,使用群集索引(clustered),以减少多个用户添加记录到该表的最后一页上,在表尾产生热点,造成死锁。这类表多为往来账的流水表,其特点是在交换期间需要在表尾追加大量的记录,并且对已添加的记录不做或较少做删除操作。          
3>对单张表中记录数不太多,且在交换期间select或updata较频繁的表可使用设置每页最大行的办法,减少数据在表中存放的密度,模拟行级锁,减少在该表上死锁情况的发生。这类表多为信息繁杂且记录条数少的表。
如:系统配置表或系统参数表。在定义该表时添加如下语句:           
with   max_rows_per_page=1           
在存储过程、触发器、动态SQL语句段中,若对某些整张表select操作较频繁,则可能在该表上与其他访问该表的用户产生死锁。对于检查账号是否存在,但被检查的字段在检查期间不会被更新等非关键语句,可以采用在select命令中使用at isolation read uncommitted子句的方法解决。该方法实际上降低了select语句对整张表的锁级别,提高了其他用户对该表操作的并发性。在系统高负荷运行时,该方法的效果尤为显著。           
如:select * from titles at isolation read uncommitted          
对流水号一类的顺序数生成器字段,可以先执行updata流水号字段+1,然后再执行select获取流水号的方法进行操作。

比如有两个用户一个userA,一个userB
当userA发出 select * from 表名 for update of 字段 时
这是纪录被锁住等待更新
当userB此时也发出select * from 表名 for update of 字段 时
并且for update of 的纪录已经被userA锁定
这是userB只能等待userA提交后才能获取到userA被占用的纪录
如果userA不提交userB就会无限期的等待
解决方法
userB执行select * from 表名 for update of 字段 no wait 10;
这时虽然纪录被userA锁定但是userB会等待10秒如果10秒内userA释放
了纪录,则userB获取该锁,如果10秒后userA未释放锁,则userB返回
错误消息
ora-30006:资源已被占用;执行此操作时出现wait超时

--------------------------------------------------------------------

userA锁住一条纪录
userB锁住一条纪录
userA请求锁住userB正在锁住的纪录
而同时userB请求锁住userA正在锁住的纪录
这时候就会产生死锁

--------------------------------------------------------------------

如果在事务中执行了一条不满足条件的update语句,则执行全表扫描,把行级锁上升为表级锁,多个这样的事务执行后,就很容易产生死锁。

在catch 中未抛出异常,就容易锁表

try {
    //返回list,将查询到数据放在list里
    zyBOList = (List) dabo.queryByKey(DmSxlxBO.class, sql, param);
   } catch (PersistenceCheckedException e) {
    logger.error("查询下拉属性时发生错误:" + e.getMessage());

//必须抛出异常,否则数据库就会锁表
   throw new CSSBaseBizCheckedException(00000001, e);
   }

数据库锁表及阻塞的原因和处理办法。
数据库和操作系统一样,是一个多用户使用的共享资源。当多个用户并发地存取数据时,在数据库中就会产生多个事务同时存取同一数据的情况。若对并发操作不加控制就可能会读取和存储不正确的数据,破坏数据库的一致性。加锁是实现数据库并发控制的一个非常重要的技术。在实际应用中经常会遇到的与锁相关的异常情况,当两个事务需要一组有冲突的锁,而不能将事务继续下去的话,就会出现死锁,严重影响应用的正常执行。
在数据库中有两种基本的锁类型:排它锁(Exclusive Locks,即X锁)和共享锁(Share Locks,即S锁)。当数据对象被加上排它锁时,其他的事务不能对它读取和修改。加了共享锁的数据对象可以被其他事务读取,但不能修改。数据库利用这两种基本的锁类型来对数据库的事务进行并发控制。
死锁的第一种情况
一个用户A 访问表A(锁住了表A),然后又访问表B;另一个用户B 访问表B(锁住了表B),然后企图访问表A;这时用户A由于用户B已经锁住表B,它必须等待用户B释放表B才能继续,同样用户B要等用户A释放表A才能继续,这就死锁就产生了。
解决方法:
这种死锁比较常见,是由于程序的BUG产生的,除了调整的程序的逻辑没有其它的办法。仔细分析程序的逻辑,对于数据库的多表操作时,尽量按照相同的顺序进行处理,尽量避免同时锁定两个资源,如操作A和B两张表时,总是按先A后B的顺序处理, 必须同时锁定两个资源时,要保证在任何时刻都应该按照相同的顺序来锁定资源。
死锁的第二种情况
用户A查询一条纪录,然后修改该条纪录;这时用户B修改该条纪录,这时用户A的事务里锁的性质由查询的共享锁企图上升到独占锁,而用户B里的独占锁由于A有共享锁存在所以必须等A释放掉共享锁,而A由于B的独占锁而无法上升的独占锁也就不可能释放共享锁,于是出现了死锁。这种死锁比较隐蔽,但在稍大点的项目中经常发生。如在某项目中,页面上的按钮点击后,没有使按钮立刻失效,使得用户会多次快速点击同一按钮,这样同一段代码对数据库同一条记录进行多次操作,很容易就出现这种死锁的情况。
解决方法:
1、对于按钮等控件,点击后使其立刻失效,不让用户重复点击,避免对同时对同一条记录操作。
2、使用乐观锁进行控制。乐观锁大多是基于数据版本(Version)记录机制实现。即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个“version”字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。乐观锁机制避免了长事务中的数据库加锁开销(用户A和用户B操作过程中,都没有对数据库数据加锁),大大提升了大并发量下的系统整体性能表现。Hibernate 在其数据访问引擎中内置了乐观锁实现。需要注意的是,由于乐观锁机制是在我们的系统中实现,来自外部系统的用户更新操作不受我们系统的控制,因此可能会造成脏数据被更新到数据库中。
3、使用悲观锁进行控制。悲观锁大多数情况下依靠数据库的锁机制实现,如Oracle的Select … for update语句,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。如一个金融系统,当某个操作员读取用户的数据,并在读出的用户数据的基础上进行修改时(如更改用户账户余额),如果采用悲观锁机制,也就意味着整个操作过程中(从操作员读出数据、开始修改直至提交修改结果的全过程,甚至还包括操作员中途去煮咖啡的时间),数据库记录始终处于加锁状态,可以想见,如果面对成百上千个并发,这样的情况将导致灾难性的后果。所以,采用悲观锁进行控制时一定要考虑清楚。
死锁的第三种情况
如果在事务中执行了一条不满足条件的update语句,则执行全表扫描,把行级锁上升为表级锁,多个这样的事务执行后,就很容易产生死锁和阻塞。类似的情况还有当表中的数据量非常庞大而索引建的过少或不合适的时候,使得经常发生全表扫描,最终应用系统会越来越慢,最终发生阻塞或死锁。
解决方法:
SQL语句中不要使用太复杂的关联多表的查询;使用“执行计划”对SQL语句进行分析,对于有全表扫描的SQL语句,建立相应的索引进行优化。
5.小结
总体上来说,产生内存溢出与锁表都是由于代码写的不好造成的,因此提高代码的质量是最根本的解决办法。有的人认为先把功能实现,有BUG时再在测试阶段进行修正,这种想法是错误的。正如一件产品的质量是在生产制造的过程中决定的,而不是质量检测时决定的,软件的质量在设计与编码阶段就已经决定了,测试只是对软件质量的一个验证,因为测试不可能找出软件中所有的BUG。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
引言 1 目的 informix9.3升级到informix9.4升级过程描述,作为对数据库升级过程的技术文档。 2 使用范围 本文档适用于指导系统工程部的工程师做informix数据库的升级。 3 参考文档 编号 资料名称 作者 出版单位 001 《IBM Informix migration Guide》 不详 IBM 4 过程描述 4.1 冷备份Informix程序目录 4.1.1 将/opt/informix目录打包tar,tar cvf ids93.tar /opt/informix/* ,备份到/backup和磁带库各一份。 4.1.2 将环境变量文件/opt/informix/.cshrc, /opt/informix/etc/sqlhosts, /opt/informix/etc/onconfig.sxdb等打包备份,备份到/backup和磁带库各一 4.2 检查和验证一致性 4.2.1 确定保留页 oncheck –cr 4.2.2 显示用户数据区域中每个Sb空间块的大小,已使用空间的总量和可用空间量。 oncheck –ce 4.2.3 确定系统目录表 oncheck –cc database_name 4.2.4 确定数据页和索引 oncheck –cDI database_name(若遇到无法修复的问题,请备份出数据删除重建表) 4.3 数据库的关闭和数据库的全备 4.3.1 数据库放到单用户模式下 onmode –sy 4.3.2 等待所有已连接到数据库的用户退出(需要开发部门配合结束采集程序) 4.3.2.1 将逻辑日志切换到下一个 onmode –l 4.3.3 执行完全检查点并释放逻辑日志文件 onmode –c 4.3.4 热备份Informix数据库 执行零级备份(到磁带库)$INFORMIXDIR/bin/onbar -b -w -L 0 4.3.5 备份逻辑日志 onbar –b –l 4.3.5.1 冷备份Informix数据库 cd /usr2/backup/dbexport mkdir perf gmcookdb c_unicom workflow3 offcdb … cd perf/ nohup dbexport -c -o ./ perf dbexport -c -o /usr2/backup/dbexport/c_perf c_perf SXdb2% dbexport -c -o /usr2/backup/dbexport/c_perf c_perf -425 - Database is currently opened by another user. -107 - ISAM error: record is locked. SXdb2% dbexport -c -o /usr2/backup/dbexport/c_perf c_perf -27002 - No connections are allowed in Dynamic Server quiescent mode. ********************************************************** 4.3.6 关闭数据库 onmode –yuk 4.4 操作系统环境检查 4.4.1 检查G网数据库服务器和应用服务器root和informix用户环境下是否有 自动执行的脚本,在对数据库进行操作 crontab –l(如果有注释掉) 4.4.2 检查G网数据库服务器,采集和应用服务器后台是否有进程对数据库进行操作 例如: ps –ef |grep dbaccess kill –9 PID 4.4.3 在G网数据库服务器上清除SNMP进程 ps –ef|grep snmp kill –9 PID 注意操作系统环境检查需要开发部配合完成 4.5 数据库服务器上覆盖安装64位的Informix9.4步骤: 4.5.1 执行ids_install,选择①安装IDS 9.4; 4.5.2 将IDS2000.tar解包; 4.5.3 执行installserver安装IDS 9.40FC5,安装细节与《IDS 9.3安装启动说明书》相同。 4.5.4 启动Informix 9.4数据库,oninit(不加参数 -i) 4.6 验证新数据库的正确性 4.6.1 验证新数据库能购正常运行。大约耗时1个小时,影响现有系统。 4.7 启用新数据库 检验配置应用、采集、上报等程序连接新安装的Informix 9.4,启用新安装的Informix 9.4数据库。大约耗时1个小时,影响现有系统。 4.8 库更新操作 dbaccess ->Query-language->选择库->执行update statistics 5 升级失败,启用旧版本数据库 5.1 从Informix程序目录的打包备份中恢复 5.1.1 清空/opt/informix目录,从“4.1.1”中的冷备份ids93.tar中解包,将程序文件恢复至/opt/informix目录。 5.1.2 检查Informix 9.3的关键的环境变量和配置文件/opt/Informix/.cshrc, /opt/informix/etc/sqlhosts, /opt/informix/etc/onconfig.hbdb等。 5.1.3 检查所有配置无误,启动Informix 9.3数据库oninit。 5.2 恢复ids93.tar不成功,重新安装Informix 9.3,重建数据库,从热备份或冷备份中恢复数据库 5.2.1 恢复失败,重新安装informix9.3数据库应用软件,重新创建数据库,热备份(on-bar)或冷备份(dbimport)中恢复数据库. 5.2.2 使用ON-Bar恢复数据库 onbar恢复之前,确保重建的数据库的名称和大小符合G网配置.例如: touch /wourkground/datadb/perfdbschk01 chmod 660 /wourkground/datadb/perfdbschk01 onspaces -c -d perfdbs -p /wourkground/datadb/perfdbschk01 -o 0 -s 1024000 5.2.3 命令格式(onbar –l) 5.2.4 使用dbimport恢复数据库 dbimport恢复不需要创建DBS,导入库时可以指定创建库的位置,例如: dbimport –c sxperf -i /backup/dbexport/port -d /opt/informix/user/hbperfchk01 6 附录:数据库升级记录 1. 将/opt/informix/*以及/opt/informix/etc/* tar到/usr2/backup目录下; 2. 使用jbpSA将第一步tar的文件备份到磁带上; 3. 检查和验证一致性 oncheck -cr oncheck –ce oncheck -cc adapter_bell_corba >oncheck.cc.adapter_bell_corba oncheck -cc oam>oncheck.cc.oam oncheck -cc adapter_cap_corba >oncheck.cc.adapter_cap_corba oncheck -cc offcdb >oncheck.cc.offcdb oncheck -cc adapter_moto_corba >oncheck.cc.adapter_moto_corba oncheck -cc sysmaster >oncheck.cc.sysmater oncheck -cc adapter_zte_corba >oncheck.cc.adpater_zte_corba oncheck -cc sysuser >oncheck.cc.sysuser oncheck -cc adpindb >oncheck.cc.adpindb oncheck -cc sysutils >oncheck.cc.sysutils oncheck -cc workflow3 >oncheck.cc.workfolow3 oncheck -cc c_unicom >oncheck.cc.c_unicom oncheck -cc dbmonitor >oncheck.cc.dbmonitor oncheck -cc gmcookdb >oncheck.cc.gmcookdb oncheck -cc c_perf >oncheck.cc.c_perf oncheck -cDI adapter_bell_corba >oncheck.cDI.adapter_bell_corba oncheck -cDI oam>oncheck.cDI.oam oncheck -cDI adapter_cap_corba >oncheck.cDI.adapter_cap_corba oncheck -cDI offcdb >oncheck.cDI.offcdb oncheck -cDI adapter_moto_corba >oncheck.cDI.adapter_moto_corba oncheck -cDI sysmaster >oncheck.cDI.sysmater oncheck -cDI adapter_zte_corba >oncheck.cDI.adpater_zte_corba oncheck -cDI sysuser >oncheck.cDI.sysuser oncheck -cDI adpindb >oncheck.cDI.adpindb oncheck -cDI sysutils >oncheck.cDI.sysutils oncheck -cDI workflow3 >oncheck.cDI.workfolow3 oncheck -cDI c_unicom >oncheck.cDI.c_unicom oncheck -cDI dbmonitor >oncheck.cDI.dbmonitor oncheck -cDI gmcookdb >oncheck.cDI.gmcookdb oncheck -cDI c_perf >onchec.cc.c_perf ------正确的命令应该为oncheck -cc c_perf >oncheck.cDI.c_perf 生成的文件存放在/usr2/backup/check_20060417_log目录下 检查耗时:20060417 22:37分~

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值