背景
项目中Oracle使用druid数据库连接池,为了提高访问数据库的效率,开启了PreparedStatement Cache。但是随着项目的运行,也出现了一些问题,在项目运行的过程中,给表添加字段后,会一直出现"ORA-17401:违反协议"异常。应用重启后,不再抛出。
问题描述
在应用持续运行中,给生产库中的某张表添加字段,然后涉及该表查询的地方不断抛出"ORA-17401:违反协议"异常,直到重启引用。
异常描述
"ORA-17401:违反协议"是TTC(Two-Task Common)的一个错误。TTC是oracle通信协议里较为靠上的一层,用于处理客户端与数据库服务器通信时的协议差异,比如转换字符集和数据类型。用以确保Oracle的客户端和服务器在不同的网络交互模式(比如TCP/IP、共享内存)下可以透明地交互。
问题定位
- 重启应用异常不再抛出,反应出重新建立数据库链接能够修复该错误;
- 部分项目使用其他连接池没有此问题,而druid与之最大的差异就是开启了PreparedStatement Cache。PreparedStatement实际上避免了相同sql语句重复解析和建立游标,同时也复用了相同的数据结构。那么当服务器端的表结构即数据结构发生改变时,很有可能TTC就会无法处理这种改变。而使用普通的Statement时,因为不存在数据结构的复用,所以也就不会出现"违反协议异常"。
所以,根据以上两点,关闭了PreparedStatement Cache后,重复之前错误的流程,不再抛出该异常。
但是选择druid就是为了其PreparedStatement Cache这个特性,然后只能继续找问题,最后查阅代码,发现之前的mapper.xml中,查询都是直接使用的select *,对部分改为select [字段] 后,发现问题不再出现,但是和其他开发人人员沟通后,认为将目前项目所有的查询修改,工作量较大,风险较高,而且同样的查询在每个项目中都有,也不想进行重构,所以,只能用其他的办法来解决。
最终解决方案
在和其他人员沟通后,只能将修改后的影响降为最低,所以只能通过如下代码,在每次修改表结构后,主动清除每个应用服务器上每个数据库连接的PreparedStatement Cache,以此来尽量避免由用户行为触发异常的出现。
@Resource(name = "druidDataSource") private DruidDataSource druidDataSource; public void clearDruidStatementCache() { Log.info("DruidDataSource","清除Druid的Statement Cache Start......"); if (druidDataSource != null) { try { druidDataSource.clearStatementCache(); } catch (sqlException e) { Log.error("DruidDataSource","清除Druid的Statement Cache Error",e); } } Log.info("DruidDataSource","清除Druid的Statement Cache End......"); }
问题的查找过程
最初用的是druid1.0.25版本,根据http://www.oschina.net/news/79452/druid-1-0-27中的信息,在1.0.27版本中,进行了如下修改
根据其中所说,如果执行出错,可以从PSCache中移除,所以将版本换到了1.0.27,但是问题还是依然存在,只好继续查。
根据http://tomcat.apache.org/tomcat-5.5-doc/jndi-datasource-examples-howto#Oracle_8i,_9i_&_10g,注意如下内容
For Oracle 9i onwards you should useoracle.jdbc.OracleDriver
rather thanoracle.jdbc.driver.OracleDriver
as Oracle have stated thatoracle.jdbc.driver.OracleDriver
is deprecated and support for this driver class will be discontinued in the next major release.
而我们用的是oracle.jdbc.driver.OracleDriver,并不是oracle.jdbc.OracleDriver,但是并不一定要进行修改。
这是因为根据https://github.com/alibaba/druid/issues/1598中所说,在1.0.29版本中已经兼容了两种写法,不过我还是按要求改成了正确的。
以上基本就是整个问题的查找过程,不过最后因为种种原因,暂时只是采取了一种比较不合理的方法来规避,尽量减少用户来触发从而降低体验。