校园网站建设培训简讯,凡客诚品商城,长沙平台搭建公司,网站开发招标前言
大概故事是这样的#xff0c;PostgreSQL数据库#xff0c;表结构#xff1a;
create table t1(a varchar);然后使用标准的Java jdbc去插入数据#xff0c;其基本代码如下#xff1a;
import java.sql.*;
public class PgDoubleTest {public static void main(Stri…
前言
大概故事是这样的PostgreSQL数据库表结构
create table t1(a varchar);然后使用标准的Java jdbc去插入数据其基本代码如下
import java.sql.*;
public class PgDoubleTest {public static void main(String[] args) {Connection connection null;ResultSet resultSet null;PreparedStatement ps null;try {// 连接到数据库connection DriverManager.getConnection(jdbc:postgresql://192.168.0.6:5432/mydb, mydb, test123);String sqlinsert into t1 values(?);ps connection.prepareStatement(sql);ps.setObject(1,new Double(48));ps.execute();ps.setObject(1,new Double(48.1));ps.execute();ps.setObject(1,new Double(48.9));ps.execute();} catch (SQLException e) {e.printStackTrace();} finally {try {if (ps ! null) {ps.close();}if (connection ! null) {connection.close();}} catch (SQLException e) {e.printStackTrace();}}}
}发现 PostgreSQL11和12及以后的版本插入到表t1中的记录值不一样。
PG11中的结果
mydb# select * from t1;a
---------------------4848.100000000000001448.8999999999999986
(3 rows)PG12及后续版本的结果
mydb# select * from t1;a
------4848.148.9
(3 rows)这个问题如何分析
分析
上边的Java代码片段采用的都是完全相同的jdbc driver: dependencygroupIdorg.postgresql/groupIdartifactIdpostgresql/artifactIdversion42.7.3/version/dependency有几种可能性驱动层根据服务器端不同的版本发送的数据就完全不同。或者服务器端根据客户端来的数据处理有所不同。
检查下SQL日志
我们分别打开postgresql.conf开启记录所有的SQL
log_statement all # none, ddl, mod, all
于是看到PG11的日志是这样的
2024-03-30 11:20:46.235 CST [13553] LOG: execute unnamed: SET extra_float_digits 3
2024-03-30 11:20:46.236 CST [13553] LOG: execute unnamed: SET application_name PostgreSQL JDBC Driver
2024-03-30 11:20:46.262 CST [13553] LOG: execute unnamed: insert into t1 values($1)
2024-03-30 11:20:46.262 CST [13553] DETAIL: parameters: $1 48
2024-03-30 11:20:46.265 CST [13553] LOG: execute unnamed: insert into t1 values($1)
2024-03-30 11:20:46.265 CST [13553] DETAIL: parameters: $1 48.1000000000000014
2024-03-30 11:20:46.266 CST [13553] LOG: execute unnamed: insert into t1 values($1)
2024-03-30 11:20:46.266 CST [13553] DETAIL: parameters: $1 48.8999999999999986
2024-03-30 11:21:11.696 CST [13555] LOG: statement: select * from t1;而PG12的日志是这样的
2024-03-30 10:45:30.172 CST [2700] LOG: execute unnamed: SET extra_float_digits 3
2024-03-30 10:45:30.173 CST [2700] LOG: execute unnamed: SET application_name PostgreSQL JDBC Driver
2024-03-30 10:45:30.197 CST [2700] LOG: execute unnamed: insert into t1 values($1)
2024-03-30 10:45:30.197 CST [2700] DETAIL: parameters: $1 48
2024-03-30 10:45:30.199 CST [2700] LOG: execute unnamed: insert into t1 values($1)
2024-03-30 10:45:30.199 CST [2700] DETAIL: parameters: $1 48.1
2024-03-30 10:45:30.200 CST [2700] LOG: execute unnamed: insert into t1 values($1)
2024-03-30 10:45:30.200 CST [2700] DETAIL: parameters: $1 48.9也就是说到了服务器端绑定参数值的时候就已经不同了。PG11里头传的字符串值已经有了变化了。很有意思的东西。我们不忙着下结论。
既然Server端的log不能判定我们可以看看driver层的详细的log信息。这个需要设置logging.properties. 详情可以参考文档https://jdbc.postgresql.org/documentation/logging/
查看JDBC客户端driver层日志
PG12的相关日志有如下关键内容
三月 31, 2024 3:49:55 下午 org.postgresql.Driver connect
详细: Connecting with URL: jdbc:postgresql://192.168.0.6:5555/mydb?loggerLevelTRACEloggerFilejdbc.log
三月 31, 2024 3:49:55 下午 org.postgresql.jdbc.PgConnection init
详细: PostgreSQL JDBC Driver 42.7.3
三月 31, 2024 3:49:55 下午 org.postgresql.jdbc.PgConnection setDefaultFetchSize
详细: setDefaultFetchSize 0非常详细: BE ParameterStatus(server_version 12.11)
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl receiveParameterStatus
非常详细: BE ParameterStatus(session_authorization mydb)
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl receiveParameterStatus
非常详细: BE ParameterStatus(standard_conforming_strings on)
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl receiveParameterStatus
非常详细: BE ParameterStatus(TimeZone Asia/Shanghai)
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl readStartupMessages
非常详细: BE BackendKeyData(pid8,855,ckey-1,722,642,031)
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl receiveRFQ
非常详细: BE ReadyForQuery(I)
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl execute
非常详细: simple execute, handlerorg.postgresql.core.SetupQueryRunner$SimpleResultHandler614ddd49, maxRows0, fetchSize0, flags1,047
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl sendParse
非常详细: FE Parse(stmtnull,querySET extra_float_digits 3,oids{})
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl sendBind
非常详细: FE Bind(stmtnull,portalnull)
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl sendExecute
非常详细: FE Execute(portalnull,limit1)
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl sendSync
非常详细: FE Sync
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl processResults
非常详细: BE ParseComplete [null]
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl processResults
非常详细: BE BindComplete [unnamed]
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl receiveCommandStatus
非常详细: BE CommandStatus(SET)
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl receiveRFQ
非常详细: BE ReadyForQuery(I)
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl execute
非常详细: simple execute, handlerorg.postgresql.core.SetupQueryRunner$SimpleResultHandler711f39f9, maxRows0, fetchSize0, flags1,047
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl sendParse
非常详细: FE Parse(stmtnull,querySET application_name PostgreSQL JDBC Driver,oids{})
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl sendBind
....
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl receiveRFQ
非常详细: BE ReadyForQuery(I)
三月 31, 2024 3:49:55 下午 org.postgresql.jdbc.PgConnection init
非常详细: types using binary send OID_ARRAY,UUID,BYTEA,INT8,INT2,INT4,POINT,TIMESTAMP,BOX,TIMESTAMPTZ,NUMERIC,BYTEA_ARRAY,INT2_ARRAY,INT4_ARRAY,TEXT_ARRAY,TIMETZ,VARCHAR_ARRAY,INT8_ARRAY,TIME,FLOAT4,FLOAT8,FLOAT4_ARRAY,FLOAT8_ARRAY
三月 31, 2024 3:49:55 下午 org.postgresql.jdbc.PgConnection init
非常详细: types using binary receive OID_ARRAY,UUID,BYTEA,INT8,INT2,INT4,POINT,TIMESTAMP,BOX,TIMESTAMPTZ,NUMERIC,BYTEA_ARRAY,INT2_ARRAY,INT4_ARRAY,TEXT_ARRAY,TIMETZ,VARCHAR_ARRAY,INT8_ARRAY,DATE,TIME,FLOAT4,FLOAT8,FLOAT4_ARRAY,FLOAT8_ARRAY
三月 31, 2024 3:49:55 下午 org.postgresql.jdbc.PgConnection init
非常详细: integer date/time true
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl execute
非常详细: simple execute, handlerorg.postgresql.jdbc.PgStatement$StatementResultHandler2353b3e6, maxRows0, fetchSize0, flags17
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl sendParse
非常详细: FE Parse(stmtnull,queryinsert into t1 values($1),oids{701})
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl sendBind
非常详细: FE Bind(stmtnull,portalnull,$1(48.0::double precision),typeFLOAT8)
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl sendDescribePortal
非常详细: FE Describe(portalnull)
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl sendExecute
非常详细: FE Execute(portalnull,limit0)
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl sendSync
非常详细: FE Sync
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl processResults
非常详细: BE ParseComplete [null]
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl processResults
非常详细: BE BindComplete [unnamed]
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl processResults
非常详细: BE NoData
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl receiveCommandStatus
非常详细: BE CommandStatus(INSERT 0 1)
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl receiveRFQ
非常详细: BE ReadyForQuery(I)
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl execute
非常详细: simple execute, handlerorg.postgresql.jdbc.PgStatement$StatementResultHandler42f93a98, maxRows0, fetchSize0, flags17
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl sendParse
非常详细: FE Parse(stmtnull,queryinsert into t1 values($1),oids{701})
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl sendBind
非常详细: FE Bind(stmtnull,portalnull,$1(48.1::double precision),typeFLOAT8)
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl sendDescribePortal
非常详细: FE Describe(portalnull)
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl sendExecute
非常详细: FE Execute(portalnull,limit0)
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl sendSync
非常详细: FE Sync
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl processResults
非常详细: BE ParseComplete [null]
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl processResults
非常详细: BE BindComplete [unnamed]
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl processResults
非常详细: BE NoData
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl receiveCommandStatus
非常详细: BE CommandStatus(INSERT 0 1)
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl receiveRFQ
非常详细: BE ReadyForQuery(I)
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl execute
非常详细: simple execute, handlerorg.postgresql.jdbc.PgStatement$StatementResultHandlerc46bcd4, maxRows0, fetchSize0, flags17
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl sendParse
非常详细: FE Parse(stmtnull,queryinsert into t1 values($1),oids{701})
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl sendBind
非常详细: FE Bind(stmtnull,portalnull,$1(48.9::double precision),typeFLOAT8)我们看到上边有明显的
非常详细: BE ParameterStatus(server_version 12.11)
非常详细: FE Bind(stmtnull,portalnull,$1(48.0::double precision),typeFLOAT8)
非常详细: FE Bind(stmtnull,portalnull,$1(48.1::double precision),typeFLOAT8)
非常详细: FE Bind(stmtnull,portalnull,$1(48.9::double precision),typeFLOAT8)
FE Parse(stmtnull,querySET extra_float_digits 3,oids{})再看看PG11下边的详细日志
三月 31, 2024 3:59:17 下午 org.postgresql.core.v3.QueryExecutorImpl receiveParameterStatus
非常详细: BE ParameterStatus(server_version 11.22 [by Sean])
。。
三月 31, 2024 3:59:17 下午 org.postgresql.core.v3.QueryExecutorImpl sendBind
非常详细: FE Bind(stmtnull,portalnull,$1(48.0::double precision),typeFLOAT8)
三月 31, 2024 3:59:17 下午 org.postgresql.core.v3.QueryExecutorImpl sendDescribePortal
非常详细: FE Describe(portalnull)
三月 31, 2024 3:59:17 下午 org.postgresql.core.v3.QueryExecutorImpl sendExecute
非常详细: FE Execute(portalnull,limit0)
三月 31, 2024 3:59:17 下午 org.postgresql.core.v3.QueryExecutorImpl sendSync
非常详细: FE Sync
三月 31, 2024 3:59:17 下午 org.postgresql.core.v3.QueryExecutorImpl processResults
非常详细: BE ParseComplete [null]
三月 31, 2024 3:59:17 下午 org.postgresql.core.v3.QueryExecutorImpl processResults
非常详细: BE BindComplete [unnamed]
三月 31, 2024 3:59:17 下午 org.postgresql.core.v3.QueryExecutorImpl processResults
非常详细: BE NoData
三月 31, 2024 3:59:17 下午 org.postgresql.core.v3.QueryExecutorImpl receiveCommandStatus
非常详细: BE CommandStatus(INSERT 0 1)
三月 31, 2024 3:59:17 下午 org.postgresql.core.v3.QueryExecutorImpl receiveRFQ
非常详细: BE ReadyForQuery(I)
三月 31, 2024 3:59:17 下午 org.postgresql.core.v3.QueryExecutorImpl execute
非常详细: simple execute, handlerorg.postgresql.jdbc.PgStatement$StatementResultHandler42f93a98, maxRows0, fetchSize0, flags17
三月 31, 2024 3:59:17 下午 org.postgresql.core.v3.QueryExecutorImpl sendParse
非常详细: FE Parse(stmtnull,queryinsert into t1 values($1),oids{701})
三月 31, 2024 3:59:17 下午 org.postgresql.core.v3.QueryExecutorImpl sendBind
非常详细: FE Bind(stmtnull,portalnull,$1(48.1::double precision),typeFLOAT8)
三月 31, 2024 3:59:17 下午 org.postgresql.core.v3.QueryExecutorImpl sendDescribePortal
非常详细: FE Describe(portalnull)
三月 31, 2024 3:59:17 下午 org.postgresql.core.v3.QueryExecutorImpl sendExecute
非常详细: FE Execute(portalnull,limit0)
三月 31, 2024 3:59:17 下午 org.postgresql.core.v3.QueryExecutorImpl sendSync
非常详细: FE Sync
三月 31, 2024 3:59:17 下午 org.postgresql.core.v3.QueryExecutorImpl processResults
非常详细: BE ParseComplete [null]
三月 31, 2024 3:59:17 下午 org.postgresql.core.v3.QueryExecutorImpl processResults
非常详细: BE BindComplete [unnamed]
三月 31, 2024 3:59:17 下午 org.postgresql.core.v3.QueryExecutorImpl processResults
非常详细: BE NoData
三月 31, 2024 3:59:17 下午 org.postgresql.core.v3.QueryExecutorImpl receiveCommandStatus
非常详细: BE CommandStatus(INSERT 0 1)
三月 31, 2024 3:59:17 下午 org.postgresql.core.v3.QueryExecutorImpl receiveRFQ
非常详细: BE ReadyForQuery(I)
三月 31, 2024 3:59:17 下午 org.postgresql.core.v3.QueryExecutorImpl execute
非常详细: simple execute, handlerorg.postgresql.jdbc.PgStatement$StatementResultHandlerc46bcd4, maxRows0, fetchSize0, flags17
三月 31, 2024 3:59:17 下午 org.postgresql.core.v3.QueryExecutorImpl sendParse
非常详细: FE Parse(stmtnull,queryinsert into t1 values($1),oids{701})
三月 31, 2024 3:59:17 下午 org.postgresql.core.v3.QueryExecutorImpl sendBind
非常详细: FE Bind(stmtnull,portalnull,$1(48.9::double precision),typeFLOAT8)找出关键的几行
非常详细: BE ParameterStatus(server_version 11.22 [by Sean])
非常详细: FE Bind(stmtnull,portalnull,$1(48.0::double precision),typeFLOAT8)
非常详细: FE Bind(stmtnull,portalnull,$1(48.1::double precision),typeFLOAT8)
非常详细: FE Bind(stmtnull,portalnull,$1(48.9::double precision),typeFLOAT8)
FE Parse(stmtnull,querySET extra_float_digits 3,oids{})比较后的总结、模拟重现与深挖
从驱动层来看发送过去的内容或指令应该是一样的。只是Server端对于类似于如下的binding分别有精度损失或没有精度损失
$1(48.1::double precision),typeFLOAT8)
我们看看PG11下边的一些行为
show extra_float_digits;extra_float_digits
--------------------0
(1 row)select 48.1::float8;float8
--------48.1
(1 row)SET extra_float_digits 3;
SET
mydb# select 48.9::float8;float8
---------------------48.8999999999999986
(1 row)
看到了吗在extra_float_digits 3;时结果就出来了。48.8999999999999986
而到了PG12结果是这样
mydb# show extra_float_digits;extra_float_digits
--------------------1
(1 row)mydb# select 48.1::double precision;float8
--------48.1
(1 row)mydb# SET extra_float_digits 3;
SET
mydb# select 48.1::double precision;float8
--------48.1
(1 row)所以 从上边的分析来看是server端对于extra_float_digits 3时在字符串转到float8 (double precision)时的行为不一样导致的。
至此真相大白。
我们也可以看下参数extra_float_digits的真实含义
https://postgresqlco.nf/doc/zh/param/extra_float_digits/ Sets the number of digits displayed for floating-point values 这个参数调整用于文本输出浮点值的位数包括float4float8以及几何数据类型。 如果值为1(默认值)或更高浮点值被输出为最短-精度格式参见datatype-float。实际生成的位数只取决于输出的值而不取决于此参数的值。float8 值最多需要 17 位数字float4值最多需要9位数字。这种格式既快速又精确在正确读取时精确地保留了原始的二进制浮点值。为了历史兼容性允许的值最大为3。 如果值为零或负则输出四舍五入为给定的十进制精度。使用的精度是根据此参数的值减小的类型(FLT_DIG或DBL_DIG视情况而定的标准位数。例如指定 -1 将导致float4 值输出四舍五入为 5 位有效数字而float8值四舍五入为 14 位。 此格式较慢不会保留二进制浮点值的所有位但可能令人更易于阅读。 此参数的含义以及其默认值在 PostgreSQL 12 中发生了变化; 参见 datatype-float 以便进一步讨论。 我在PostgreSQL, 留心对应的BigDecimal,Float,Double几种Java类型也有关于float8类型的详细介绍 。
总结
分析这类问题在源码介入之前通过两端的日志进行对比分析往往也能抓到蛛丝马迹。进而可以进一步进行挖掘和分析。上边的Java代码最后都可以通过简单的SQL片段来重现。
参考
https://www.enterprisedb.com/blog/jdbc-logging-using-javautillogging
https://jdbc.postgresql.org/documentation/logging/
https://postgresqlco.nf/doc/zh/param/extra_float_digits/