案例再现:
一家企业已经部署了ERP系统,其后台就用到了Oracle数据库系统。不过这个ERP系统没有人事管理系统模块。企业后来为了加强人事管理,故又采用了一款人事管理的系统。由于ERP系统是开源的,故通过简单的开发就可以跟人事管理系统进行集成。由于人事管理跟ERP系统的相关模块基本上没有很大的内在联系,所以最后只有User表是与人事管理系统共用的,其他都是通过视图的形式进行数据交流。但是当企业开始用这个系统的时候,就出现了问题。如采购员需要维护供应商联系人(其需要用到User表)的时候,往往会发现数据库会出现长时间的停顿现象。有时候甚至需要到第二天才能够顺利更改或者添加供应商联系人。
数据库管理员刚开始怀疑是否是服务器性能的问题。但是检查了CPU以及内存的使用情况来看,问题不是发生在性能上面。再经过深入细致的调查,才发现是锁冲突导致了User表反映的迟缓。这个人事管理系统本来就是人事管理人员在用,基本上就是一个单机版的系统。这个应用程序在设计的时候,可能出于数据库兼容性的考虑,故在表级别上实现了锁。也就是说,即使对User表其中的一条记录进行修改,应用程序也会为整个User表进行加锁。如此的话,当人事管理人员在更该员工信息的话,这个表其他用户就不能够更改,直到人事管理人员完成更改。而由于供应商联系人信息其也是存储在User表中,为此采购员就会感觉到User表会出现长时间的停顿显现。
问题解析:
数据库锁主要是用来解决并发行访问可能出现的问题。根据加锁内容的不同,可以在不同的级别上实现锁。如可以在整个表上实现加锁;也可以对特定的行实现加锁。有些应用软件为了兼容于市场上每个基于SQL语句的数据库,会例行公事地在表级别上而不是在航级别上锁定资源。采用了不必要的高级别加锁机制从而导致了不必要的锁冲突。所以所在应用程序设计的时候需要注意因为锁设置不当而给数据库造成的负面影响。
那么应用程序的哪些不恰当设计会增加锁冲突发生的机率呢?根据笔者的经验,如果应用程序如下几个方面设计不当的话,则会大大增加锁冲突发生的机会。
1、事务运行时间过长导致锁冲突。这是最容易发生的情况。如在ERP系统中,有一个批量结束采购订单的作业。因为在采购订单管理中,有时候会出现尾数的情况。如采购订单明细中有一个产品,采购订单上写着是1998套,而中18套可是出于损耗率而考虑购买的。但是供应商由于生产线的限制,其可能只供应了1995套,即少供应商了3套。此时由于供应商交货数量没有达到采购数量,故此时系统中这张采购订单或者采购订单中的这个采购条目永远不会结束。这会导致管理上很大的麻烦。如采购员可能需要定期导一份未结案采购明细表以追踪物料。可此时哪些尾数问题,会导致导出来的报表中内容虚多。另外在根据采购单转收货单作业中,这些未结案的采购订单也会老是在那个窗口中显示,这给系统用户选择增添了一定的困难。故系统中设计了这个作业,用来帮助用户批量的结束采购订单。可是这个程序如果设计的不好的话,会造成比较大的麻烦。因为这个作业其需要更新好多张表,如采购明细表、采购订单头表等等。而且还会涉及到比较复杂的业务逻辑,如需要判断采购行行中的所有采购条目是否都已经结束,只有在全部结束后程序才会更新采购订单头的结束码字段,即这张采购订单全部结束。如果所有明细没有全部结束,还有部分产品没有到料的话,那么只结束这个具体的条目,而采购订单没有结案。由于涉及到的表比较多、业务逻辑比较复杂,如果记录再比较多的话,那么此时这个作业运行就会需要比较长的时间。而在执行这个作业的时候,系统会把这个作业所涉及表中的记录锁起来,不允许其他用户进行更改。此时若其他用户需要对他们更改的话(更改同一条记录的其他字段),就会造成锁冲突。其他用户必须等待这个作业完成之后才能够更改相关的记录。
所以说,应用程序设计时设计事务比较繁琐,将使得锁冲突的几率大大提高。为此笔者建议各位应用程序开发人员,在设计事务时,最好能够遵循简短化的操作。如相关的业务逻辑、所涉及到的表等等,能够简单就简单,能够少就少。如果真的需要很多步骤才能够完成一个事务的话,那就可以适当考虑把这个事务分成几个独立的事务来完成。另外在给企业用户培训的时候,最好能够说明这个问题。建议企业员工在执行这些事务的时候,选择一个合适的时间。如可以让员工在下班后执行这个作业,以降低对其他用户的影响。
2、由于所级别设置不当所造成的锁冲突。在Oracle10G以后的版本中,一个事务能够锁定一行、多行或者整个表。虽然用户也可以手工对行或者表进行加锁,但是Oracle数据库服务器能够自动在尽可能低的级别上锁定必要的行,以保证数据完整性,并最小化与其他可能需要访问该表中其他行的事务之间的冲突。
如笔者一开始提到的案例,一些应用软件为了提高其软件的兼容性,提高其软件支持的数据库系统,就会例行公事的采取高级别的锁机制。在本来只需对行进行加锁的情况下,而对表进行了加锁。这就会影响其他用户对该表进行维护工作。为此笔者认为,应用程序在设计的时候,考虑了软件与数据库系统的兼容性,那本来是件好事情。但是在设计的时候能否再到位一点的。如可以根据数据库接口的不同,来考虑到底是采取行级别锁呢还是采取表级别锁。而不能够一刀切,不分青红皂白一律采用表锁。这会大大降低应用程序的性能,是一个不合理的设计方法。也即是说,应用程序应该可以根据所采用数据库的不同(甚至版本的不同)来判断这些数据库是否支持行锁。如果支持行锁的话,则就只需要对行加锁即可。其实要实现这个也不是很难,只需要在应用程序初始化的时候进行设置即可。先进一点的直接可以从数据库接口中获得数据库的品牌与版本信息,并进行相应的调整。迟钝一点的需要系统管理员在初始化参数中手工指定所采用的数据库品牌与版本。即使采取后面这种笨办法也要比采用一刀切的表级别锁方式好得多。
3、没有严格限制用户从数据库表中更新数据。
有些软件在设计时,允许用户可以直接在数据库中修改相关的数据。这或许能够给企业带来一定的便利性,但是也会造成比较到的隐患。如某个系统管理员可能从数据库表中更新了相关的数据。但是他在更新完成后,可能是出于疏忽的原因,没有立即执行commit语句。此时利用当前会话进行查询的话,会发现数据已经被更改。而利用另外一个会话查询的时候,其查询到的仍然是更改前的数据。也就是说,此时系统管理员更改的数据还没有提交到数据库表中。此时对于所涉及到的行就会进行加锁。而且有时候出于性能等方面的考虑,数据库甚至会对整张表进行加锁。由于用户忘了执行commit指令,这个锁就会一直在那边。那么其他用户就不能够对这些记录进行任何的更改作业。#p#分页标题#e#
类似的情况笔者遇到的还是比较多的。因为有些系统管理员没有受到过专业的数据库培训,是半路出家的。为此他们对于这个锁的机制与Commit命令的功用没有直观的印象。所以他们从数据库中更新数据后,往往会忘记执行Commit命令。所以笔者认为如果把数据库开发给企业的话,可能对数据库的性能会产生比较大的影响,因为用户很容易忘记执行commit指令。而如果企业用户的更新动作都通过前台应用程序来完成,那么很少会发生这种情况了。在应用程序设计的时候,肯定会在SQL语句的最后加上一个Commit指令。这就可以防止因为没有恰当执行commit指令而导致的锁冲突事件的发生。
笔者认为由于应用程序开发与数据库设计是处于两个不同的领域。故数据库管理员需要跟应用程序开发者进行有效沟通。只有在双方共同努力下,才能够在最大限度内避免这种情况的发生。