现有数据库开发过程中对事务的控制、事务锁、行锁、表锁的发现缺乏必要的方法和手段,通过以下手段可以丰富我们处理开发过程中处理锁问题的方法。For Update和For Update of使用户能够锁定指定表或表的数据行这个功能在实际应用中具有很重要的意义,特别对于多用户多线程处理中如要先获取数据通过判断在去更新数据(这中间不允许数据发生变化)的时候这个SQL功能是唯一最佳的选择。
此外,为了解决因为For Update而引起的死锁问题,Oracle提供了select...[for update [of tab.col]] [nowait]功能,这个功能使得在执行select...for update前先检查所申请的行、表资源是否可用,如果可用则加写锁,否则直接返回Ora-54错误。这个功能也用很好的应用价值,在多线程中判断资源的可用性方面将发挥作用。
Table | For Update | For Update of A.Id |
A | 1.有where条件时,锁定条件中指定的数据行(行级封锁); 2.无where条件是,锁定表A(表级封锁)。 |
1.有where条件时,锁定条件中指定的数据行(行级封锁); 2.无where条件是,锁定表A(表级封锁)。 |
A,B | 直接封锁A,B表(表级封锁) | 1.有where条件时,封锁where条件中满足条件的A表的数据行(行级封锁),B表不锁定; 2.无where条件是,锁定A表(表级锁),B表不锁定。 |
通过对比发现,发现对于单表来说For Update和For Update of效果一样,只有在多表查询时产生差异,这个差异在于For Update of使用户能够锁定多表中的指定表或表的数据行。
以代码为例:背景:有4台线上任务服务器,处理同一个表中的数据,为了避免引起数据读写混乱,采用了for update的方式来加锁。
@SuppressWarnings("unchecked") public List<BizExpressDailyDO> fetchSomeBizExpressDaily(final String serverIp, final int some) throws DataAccessException { return (List<BizExpressDailyDO>) new TransactionTemplate(transactionManager).execute(new TransactionCallback() { @SuppressWarnings("rawtypes") public Object doInTransaction(TransactionStatus status) { // 取得锁的钥匙 getSqlMapClientTemplate().queryForObject("MS-SELECT-ACTION-LOCK-BY-LOCK-NAME-FOR-UPDATE", "bizexpress"); List<BizExpressDailyDO> bizexpresses = getSqlMapClientTemplate().queryForList( "MS-FIND-SOME-BIZ-EXPRESS", Integer.valueOf(some)); Map param = new HashMap(); param.put("serverIp", serverIp); param.put("some", Integer.valueOf(some)); getSqlMapClientTemplate().update("MS-UPDATE-SOME-BIZ-EXPRESS", param); return bizexpresses; } }); } 上面中的MS-SELECT-ACTION-LOCK-BY-LOCK-NAME-FOR-UPDATE, <!-- 锁定某个ACTION的纪录 --> <select id="MS-SELECT-ACTION-LOCK-BY-LOCK-NAME-FOR-UPDATE" parameterClass="java.lang.String"> <![CDATA[ SELECT * FROM ACTION_LOCK WHERE LOCK_NAME = #value# FOR UPDATE ]]> </select>
通这这段代码对表加锁,这样其它线程当执行到此处时会处于等待状态,直到表锁释放,这样可以限制其它线程访问biz_express_daily表执行下面sql语句。
<!-- 挑选出一些纪录等待更新 --> <select id="MS-FIND-SOME-BIZ-EXPRESS" resultMap="RM-BIZ-EXPRESS-DAILY"> <![CDATA[ SELECT * FROM BIZ_EXPRESS_DAILY WHERE SERVER_IP = '0.0.0.0' AND ROWNUM < #some# ]]> </select> <!-- 更新一些纪录的server_ip --> <update id="MS-UPDATE-SOME-BIZ-EXPRESS"> <![CDATA[ UPDATE BIZ_EXPRESS_DAILY SET SERVER_IP = #serverIp#, GMT_MODIFIED = SYSDATE WHERE SERVER_IP = '0.0.0.0' AND ROWNUM < #some# ]]> </update>
从而有效的解决了多线程并发数据库表的问题。