背景与结果:
近期有项目使用spring框架的应用从oracle移植到达梦数据库后,做数据修改相关的操作会报错:
“nested exception is dm.jdbc.driver.DMException: 试图在只读事务中修改数据”
我们抛开集群环境,从单机环境来看,究其原因实际上是达梦与oracle在针对连接上设置setReadOnly属性有不同表现有关,先直接说结论,对于使用了dm8 jdbc驱动的程序来说,需要设置增加一个jdbc属性compatibleMode=oracle就可以解决这个问题。
当然针对纯java项目来说直接添加就可以了,从第二个属性值开始后续的用&符号隔开就行了,
例如:
jdbc:dm://127.0.0.1?appname=test&compatibleMode=oracle
但是针对一些使用了xml配置文件之类框架的项目(如spring)就需要对&符号进行转义,使用&来进行代替,例如:
jdbc:dm://127.0.0.1?appname=test&compatibleMode=oracle
在这需要着重说明一下的是,这里的compatibleMode是在jdbc连接串中增加,而不是dm.ini中,虽然dm.ini中compatible_mode=2参数也是设置oracle兼容性,这两者的设置非常容易混响,但是compatible_mode是针对数据库服务端对不同数据库的的一些兼容表现,而我们今天要讨论的是jdbc驱动层的参数,需要特别注意这两者的区别。
实验对比验证:
结论其实很简单,下面我们着重讨论一下达梦与oracle表现不同的具体原因和大致实现逻辑。
首先我们从客户现场报错的jdbc日志入手看一下:
从上述图片中我们可以获取一些有用信息,首先jdbc在锁execute时抛出了异常“试图在只读事务中修改数据”,而对应的sql语句的确是update操作。这个报错很明显是由于只读事务不允许修改数据,那为什么这个事务变成了只读事务呢?再往上我们可以看到执行该sql的这个连接被设置了两个属性setReadOnly(true)和setAutoCommit(false),那看到这里我们基本知道大致原因了,这个连接被设置为只读了,当然不能修改数据了。那我们知道业务表在数据库为open状态下默认是可读可写的(除非是只读用户、没有权限之类的我们排除掉),那接口层不可能自己去设置这个只读属性,只能是应用端进行了一些设置,而应用使用了spring框架,我们姑且猜测是框架层设置了对应的只读属性。那客户明确说明同一套应用程序从oracle移植到达梦,oracle上是没问题的,到达梦上一旦涉及到修改数据就报错,这肯定是有问题的,大概率是两个数据库在接口层setReadOnly(true)上的实现方式有所不同导致的,下面我们具体来分析一下。
Spring框架测试对比:
下面我们用一个简单的sring框架的测试程序对一些数据进行修改操作,尝试模拟达梦和oracle的不同表现,其中我们在修改数据的方法上设置一下事务属性为readonly,如下图所示:
先使用dm8的驱动访问达梦数据库,的确报了相同的错误:
在执行的过程中我其实去抓取了一下对应的连接只读状态,可以看到连接是只读状态的。
select rdonly from v$sessions where appname=’test’
同样一套程序我们使用ojdbc8-12.2.0.1.jar驱动连接的oracle11g,可以看到执行成功了,
至此基本印证了我们的猜想,srping设置事务readonly属性后dm8与oracle的表现的确是不同的,我们继续查看达梦的jdbc日志可以看到在执行修改操作之前,做了与现场jdbc日志相同的操作setReadOnly(true),由此可见在spring中设置事务readonly其实在java底层就是对Connection做了一个setReadOnly(true)的操作,那说到这里问题就简化了,我们抛开spring框架,使用纯java来进行问题复现。
纯JAVA测试对比:
同样我们设置conn.setReadOnly(true)然后进行对比,在达梦上的表现为:
连接oracle的表现是:
可以看到纯java程序与spring框架在这个问题上表现是一致的,下面我们主要需要探讨的是为什么同样设置setReadOnly(true)在达梦与oracle上的表现不一致。
为何dm与oracle表现不同:
这里我们先看一下java api文档中对于setReadOnly的解释:
意思就是“把此连接设置为只读模式,作为的数据库优化的暗示”。这里面有事务隔离吗?没有,只是向数据库驱动做一个启动数据库优化的暗示,不代表一定有效。所以不应该把readOnly作为打开只读事务的判断。
搞了半天这个setReadOnly只是一个暗示啊,具体怎么实现是由数据库自己去决定的,当然不同数据库的实现是有所区别的。网上各种找资料一段操作猛如虎,顺着线索摸了一下ojdbc的驱动,发现根本没有对setReadOnly的具体实现,也就是说oracle在jdbc驱动层啥都没有干,相当于忽略了setReadOnly。反观dm8的驱动,我们从实验效果来看是把对应连接设置为readonly了,也就是这个连接上不允许修改数据。
到了这里可能就有个疑问,很多项目也都在使用spring框架访问达梦数据库,难道其他项目没有遇到这个问题吗,显然是不可能的啊!肯定有啥设置(其实也就是文章开头说的jdbc参数设置啦)可以实现与oracle相同的效果,这里我们看一下dm8 jdbc的具体实现其实就知道了。
可以看到,当设置setRreadOnly(true)时,其实就是对这个会话执行了SP_SET_SESSION_READONLY(1),对达梦数据库熟悉的朋友肯定知道一旦执行这个系统过程那当前数据库就是只读连接,不允许修改数据了。那这里其实还有一个前提条件进行判断,compatibleOracle()其实就是从读取compatibleMode是否设置为oracle,至此真相大白。
当jdbc设置属性compatibleMode=oracle时,就是兼容了oracle忽略了readonly的设置,将会话上的readOnly属性设置无效。所以当我们将应用从oracle移植到达梦数据库时,一定要注意这些兼容参数的设置。其实还有早期的comOra=true参数可以达到同等效果,目前dm8的驱动也是可以是使用的,但是官方文档的程序员手册中并没有这个comOra参数,所以建议大家还是使用compatibleMode吧。
老版本oracle驱动与dm表现是一致的(题外话):
其实在这里多说一点的是,在早期oracle9i的jdbc驱动中其实对setRreadOnly并没有忽略,进行了一些实现,如下:
可以看到在早期的oracle jdbc驱动中,setReadOnly会让oracle数据库服务端设置事务隔离级别为read only,而oracle的read Only事务隔离级别肯定也是不允许进行数据修改操作的,这一点与达梦有所不同,达梦是设置整个连接为read only,而oracle是设置事务为read only。有兴趣的朋友可以自己测试一下看看,用oracle9i的jdbc驱动在设置setReadOnly以后oracle的表现是和达梦一致的,也会报错只读的错误,但是后续的oracle jdbc驱动为什么把setReadOnly的设置摒弃了就不得而知了,但是从oracle官方文档中可以略知一二,如下图所示:
可以看到oracle后续版本并不在驱动层支持事务隔离级别了,只是在服务端支持。
结论:
综上所述,对Connection设置setReadOnly(true)后,在dm8与oracle上的表现是不同的,dm8 jdbc驱动会直接将该连接设置为只读连接,该连接上的所有事务都不允许修改数据,而oracle jdbc驱动则会直接将该设置忽略,仍然可以修改数据。当然dm8的jdbc驱动设置jdbc连接属性compatibleMode=oracle可以兼容oracle的这一设置,直接忽略setReadOnly(true)的设置,使得该连接仍然是可以修改数据的。
Oracle在会话层面并没有readonly的设置,需要从更细粒度的事务级进行设置set transaction read only。而达梦既有会话级别的readonly设置,同时也支持事务级别的只读,设置方法与oracle是相同的。当然在事务级别设置只读是从事务隔离级别出发的,而达梦的会话级只读是从限制修改数据来出发的,两者的实现目标是不同的。
文章
阅读量
获赞