上周五碰到开发的请求协助解决数据预约程序中对单头等几个表检索数据时检索条件尾数是9的数据特别慢。第一时间想到的是否以下几个问题:java
一、 数据库相应数据最多;sql
二、 数据表进行了分区,而相应数据落在的分区性能存在问题;数据库
三、 检索该批记录的会话不少;服务器
但很快排除了以上几种可能,由于后续的调查由以下发现:网络
一、 查询了相应数据,尾数0-9的数据几乎平均分布;app
二、 直接用SQLSERVER查询分析器查询速度很快;ide
到此你们基本都认为数据库不存在问题,可是以下的调查感受又让人陷入了迷茫。在相同网段的其余服务器上创建了相应的用户、表、数据进行测试,发现程序取数据速度很快,和生产数据库的区别就是数据量,还有表结构(为了测试偷懒所有字段定义为VARCHAR,后来执行程序异常才无可奈何把部分字段改为了date)。虽然测试过程不严谨,但这又彷佛能把问题的天平向数据库这边倾斜。sqlserver
就这样周末测试摸索的一天很快过去了,家里的无线网络6块钱一小时开销也实在不小,周一一大早就来到公司,回归有限宽带的怀抱,但郁闷的生产库咱们只有查询几个字段的权限,我动不了。因而联络上游数据单位,盘算着经过看执行日志等手段把问题范围进一步缩小,而要对方配合却要工做联系单。想一想仍是先花半天时间有把咱们这边的问题可能性所有扫除了才开这个联系单。性能
如何进一步缩小差距, 接下来来看看网络是否有问题,巧的是数据库服务器IP末尾是9,而偏偏是末尾为9的数据,为了排除该问题,进行了一系列的探索:测试
一、 更换测试数据库服务器,发现取数据速度正常;
二、 在测试程序客户段增长HOST映射,但取数据速度依旧;
3、在同一网段服务器部署测试程序。
以上三种测试均代表网络异常的可能性愈来愈小,至少在咱们IDC和分中心内网之间的网络互联是正常的。
再镇定想一想数据库查询为何慢,只有三个层面的缘由,数据库自己的性能问题、网络问题、客户端问题,而当今问题最多的无疑就是客户端程序异常,既然生产数据库没法作有效的调试,那么咱们得把咱们的问题尽可能扫除。
因而请开发部的侯文杰把相关代码的时间戳进一步细化,文杰几分钟内就提供了代码,当即上传测试发如今ps = conn.prepareStatement和ps.setString效率上均没有问题,而rs = ps.executeQuery();就是罪会祸首。
再来仔细审视数据,一个念头忽然闪现,既然SQLSERVER客户端查询正常,会不会是程序组装的SQL存在问题。因而当即要求文杰给我一个测试任意SQL语句的接口,经过这个接口,我能够为所欲为的测试cout(*),like等语句的性能(悲剧的是很长时间没有碰代码,个人开发环境已经没法运行)。一个小时后我获得了这个接口,接下来发现了使人兴奋的结果:
我本身组装的一条完整的SQL语句经过程序装载后执行迅速,而另一条经过JDBC SETSTRING组装的一样的SQL语句,居然仍是出奇的慢。
测试案例一:
[jdbctest] 2011-05-09 16:03:41,437 - 得到jdbc链接start...
[jdbctest] 2011-05-09 16:03:42,062 - 得到jdbc链接end...
[jdbctest] 2011-05-09 16:03:42,078 - 开始执行自定义Statement动态sql=Select TRYhead0_.TRY_ID As TRY1_0_,TRYhead0_.PRE_TRY_ID As Pre2_122_0_ From Ris.risk.TRY_HEAD TRYhead0_ Where TRYhead0_.TRY_ID = '290120040024700579'-----本身写了完整的SQL语句传入JAVA程序
[jdbctest] 2011-05-09 16:03:42,187 - com.microsoft.sqlserver.jdbc.SQLServerResultSetMetaData@7c6768
[jdbctest] 2011-05-09 16:03:42,203 - ColumnName is=TRY1_0_,and value is=290120040024700579
[jdbctest] 2011-05-09 16:03:42,203 - ColumnName is=Pre2_122_0_,and value is=000000000002748431
[jdbctest] 2011-05-09 16:03:42,203 - 结束执行自定义Statement动态sql,用时:125毫秒
[jdbctest] 2011-05-09 16:03:42,203 - 得到jdbc链接start...
[jdbctest] 2011-05-09 16:03:42,234 - 得到jdbc链接end...
[jdbctest] 2011-05-09 16:03:42,265 - conn.prepareStatement(),DYNAMIC_EXECUTE_PREPARESQL is=Select head0.TRY_ID As TRY1_0,head0.PRE_TRY_ID As Pre2_122_0_ From Ris.risk.TRY_HEAD head0 Where head0.TRY_ID = ?,耗时:62毫秒-----采用JDBC的SETSTRING方法设变量
[jdbctest] 2011-05-09 16:05:15,125 - com.microsoft.sqlserver.jdbc.SQLServerResultSetMetaData@b162d5
[jdbctest] 2011-05-09 16:05:15,125 - ColumnName is=TRY1_0,and value is=290120040014700789
[jdbctest] 2011-05-09 16:05:15,125 - ColumnName is=Pre2_122_0_,and value is=000000000002794232
[jdbctest] 2011-05-09 16:05:15,125 - 结束执行自定义prepare动态sql,用时:92922毫秒
这已经很显然了,JDBC组装的SQL语句有问题的嫌疑最大。
再查相关资料,在sqlserver jdbc 驱动的文档发现里面有这么一个参数:
SendStringParametersAsUnicode
SendStringParametersAsUnicode={true false}. Determines
whether string parameters are sent to the SQL Server database in
Unicode or in the default character encoding of the database.
True means that string parameters are sent to SQL Server in
Unicode. False means that they are sent in the default encoding,
which can improve performance because the server does not need
to convert Unicode characters to the default encoding. You
should, however, use default encoding only if the parameter
string data that you specify is consistent with the default
encoding of the database.
The default is true
string型的参数传到数据库里面默认是转换成unicode的。
立刻请文杰进行相关把SendStringParameters属性(参考附件二)设置成false(详见附一),设置后查询的性能获得了巨大的提升,原来用数分钟的查询如今只须要几时毫秒了。
执行结果以下:
测试案例二:
[jdbctest] 2011-05-09 15:42:59,812 - 开始执行自定义Statement动态sql=Select TRYhead0_.TRY_ID As TRY1_0_,TRYhead0_.PRE_TRY_ID As Pre2_122_0_ From Ris.risk.TRY_HEAD TRYhead0_ Where TRYhead0_.TRY_ID = '290120040024700579'
[jdbctest] 2011-05-09 15:42:59,890 - com.microsoft.sqlserver.jdbc.SQLServerResultSetMetaData@7c6768[jdbctest] 2011-05-09 15:42:59,906 - ColumnName is=TRY1_0_,and value is=290120040024700579
[jdbctest] 2011-05-09 15:42:59,906 - ColumnName is=Pre2_122_0_,and value is=000000000002748431
[jdbctest] 2011-05-09 15:42:59,921 - 结束执行自定义Statement动态sql,用时:109毫秒
[jdbctest] 2011-05-09 15:42:59,921 - 得到jdbc链接start...
[jdbctest] 2011-05-09 15:42:59,953 - 得到jdbc链接end...
[jdbctest] 2011-05-09 15:42:59,968 - conn.prepareStatement(),DYNAMIC_EXECUTE_PREPARESQL is=Select head0.TRY_ID As TRY1_0,head0.PRE_TRY_ID As Pre2_122_0_ From Ris.risk.TRY_HEAD head0 Where head0.TRY_ID = ?,耗时:47毫秒
[jdbctest] 2011-05-09 15:42:59,984 - com.microsoft.sqlserver.jdbc.SQLServerResultSetMetaData@b162d5
[jdbctest] 2011-05-09 15:42:59,984 - ColumnName is=TRY1_0,and value is=290120040014700789
[jdbctest] 2011-05-09 15:43:00,000 - ColumnName is=Pre2_122_0_,and value is=000000000002794232
[jdbctest] 2011-05-09 15:43:00,000 - 结束执行自定义prepare动态sql,用时:79毫秒-------------对比测试案例一能够发现速度明显提升
到此JDBC查询已经能够解决问题了,可是咱们的程序都是部署在链接池(POOL下的)
查看了下官方文档,咱们发现有以下内容:
能够经过多种方式指定链接字符串的属性:
·
当使用 DriverManager 类进行链接时,在链接 URL 中经过“名称=值”属性进行指定。
·
在 DriverManager 类中 Connect 方法的 Properties 参数中经过“名称=值”属性进行指定。
·
在驱动程序数据源的适当的 setter 方法中指定值。
咱们能够很偷懒的改以下URL解决上述问题(增长;sendStringParametersAsUnicode=false),这样程序不须要作任何更改,但我的认为测试是否有乱码仍是必须的:
zjport.datasource.risk.url=jdbc:sqlserver://192.168.1.9:1433;DatabaseName=Ris;sendStringParametersAsUnicode=false
至此问题已经解决了,但最终是什么缘由只有尾数9的数据有问题,最大的嫌疑对象是JDBC驱动存在某个未知的BUG,咱们仍然须要作后续的探讨!
附件一:修改JDBC代码的方式(文杰提供)
SendStringParameters设置前:
Class.forName(DRIVER_CLASS_NAME);
conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
SendStringParameters设置后:
Class.forName(DRIVER_CLASS_NAME);
Properties info = new Properties();
info.put("user", USERNAME);
info.put("password", PASSWORD);
info.put("sendStringParametersAsUnicode", "false");
conn = DriverManager.getConnection(URL, info);
附件二:参考文档
http://msdn.microsoft.com/zh-cn/library/ms378988(v=sql.90).aspx
http://school.cnd8.com/java/jiaocheng/11759.htm
http://technet.microsoft.com/zh-cn/library/aa342325(SQL.100).aspx