前言
大概故事是这样的,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 sql="insert 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
---------------------
48
48.1000000000000014
48.8999999999999986
(3 rows)
PG12及后续版本的结果:
mydb=# select * from t1;
a
------
48
48.1
48.9
(3 rows)
这个问题如何分析?
分析
上边的Java代码片段,采用的都是完全相同的jdbc driver:
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.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?loggerLevel=TRACE&loggerFile=jdbc.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(pid=8,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, handler=org.postgresql.core.SetupQueryRunner$SimpleResultHandler@614ddd49, maxRows=0, fetchSize=0, flags=1,047
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl sendParse
非常详细: FE=> Parse(stmt=null,query="SET extra_float_digits = 3",oids={})
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl sendBind
非常详细: FE=> Bind(stmt=null,portal=null)
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl sendExecute
非常详细: FE=> Execute(portal=null,limit=1)
三月 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, handler=org.postgresql.core.SetupQueryRunner$SimpleResultHandler@711f39f9, maxRows=0, fetchSize=0, flags=1,047
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl sendParse
非常详细: FE=> Parse(stmt=null,query="SET 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, handler=org.postgresql.jdbc.PgStatement$StatementResultHandler@2353b3e6, maxRows=0, fetchSize=0, flags=17
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl sendParse
非常详细: FE=> Parse(stmt=null,query="insert into t1 values($1)",oids={701})
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl sendBind
非常详细: FE=> Bind(stmt=null,portal=null,$1=<('48.0'::double precision)>,type=FLOAT8)
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl sendDescribePortal
非常详细: FE=> Describe(portal=null)
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl sendExecute
非常详细: FE=> Execute(portal=null,limit=0)
三月 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, handler=org.postgresql.jdbc.PgStatement$StatementResultHandler@42f93a98, maxRows=0, fetchSize=0, flags=17
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl sendParse
非常详细: FE=> Parse(stmt=null,query="insert into t1 values($1)",oids={701})
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl sendBind
非常详细: FE=> Bind(stmt=null,portal=null,$1=<('48.1'::double precision)>,type=FLOAT8)
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl sendDescribePortal
非常详细: FE=> Describe(portal=null)
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl sendExecute
非常详细: FE=> Execute(portal=null,limit=0)
三月 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, handler=org.postgresql.jdbc.PgStatement$StatementResultHandler@c46bcd4, maxRows=0, fetchSize=0, flags=17
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl sendParse
非常详细: FE=> Parse(stmt=null,query="insert into t1 values($1)",oids={701})
三月 31, 2024 3:49:55 下午 org.postgresql.core.v3.QueryExecutorImpl sendBind
非常详细: FE=> Bind(stmt=null,portal=null,$1=<('48.9'::double precision)>,type=FLOAT8)
我们看到上边有明显的:
非常详细: <=BE ParameterStatus(server_version = 12.11)
非常详细: FE=> Bind(stmt=null,portal=null,$1=<('48.0'::double precision)>,type=FLOAT8)
非常详细: FE=> Bind(stmt=null,portal=null,$1=<('48.1'::double precision)>,type=FLOAT8)
非常详细: FE=> Bind(stmt=null,portal=null,$1=<('48.9'::double precision)>,type=FLOAT8)
FE=> Parse(stmt=null,query="SET 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(stmt=null,portal=null,$1=<('48.0'::double precision)>,type=FLOAT8)
三月 31, 2024 3:59:17 下午 org.postgresql.core.v3.QueryExecutorImpl sendDescribePortal
非常详细: FE=> Describe(portal=null)
三月 31, 2024 3:59:17 下午 org.postgresql.core.v3.QueryExecutorImpl sendExecute
非常详细: FE=> Execute(portal=null,limit=0)
三月 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, handler=org.postgresql.jdbc.PgStatement$StatementResultHandler@42f93a98, maxRows=0, fetchSize=0, flags=17
三月 31, 2024 3:59:17 下午 org.postgresql.core.v3.QueryExecutorImpl sendParse
非常详细: FE=> Parse(stmt=null,query="insert into t1 values($1)",oids={701})
三月 31, 2024 3:59:17 下午 org.postgresql.core.v3.QueryExecutorImpl sendBind
非常详细: FE=> Bind(stmt=null,portal=null,$1=<('48.1'::double precision)>,type=FLOAT8)
三月 31, 2024 3:59:17 下午 org.postgresql.core.v3.QueryExecutorImpl sendDescribePortal
非常详细: FE=> Describe(portal=null)
三月 31, 2024 3:59:17 下午 org.postgresql.core.v3.QueryExecutorImpl sendExecute
非常详细: FE=> Execute(portal=null,limit=0)
三月 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, handler=org.postgresql.jdbc.PgStatement$StatementResultHandler@c46bcd4, maxRows=0, fetchSize=0, flags=17
三月 31, 2024 3:59:17 下午 org.postgresql.core.v3.QueryExecutorImpl sendParse
非常详细: FE=> Parse(stmt=null,query="insert into t1 values($1)",oids={701})
三月 31, 2024 3:59:17 下午 org.postgresql.core.v3.QueryExecutorImpl sendBind
非常详细: FE=> Bind(stmt=null,portal=null,$1=<('48.9'::double precision)>,type=FLOAT8)
找出关键的几行:
非常详细: <=BE ParameterStatus(server_version = 11.22 [by Sean])
非常详细: FE=> Bind(stmt=null,portal=null,$1=<('48.0'::double precision)>,type=FLOAT8)
非常详细: FE=> Bind(stmt=null,portal=null,$1=<('48.1'::double precision)>,type=FLOAT8)
非常详细: FE=> Bind(stmt=null,portal=null,$1=<('48.9'::double precision)>,type=FLOAT8)
FE=> Parse(stmt=null,query="SET extra_float_digits = 3",oids={})
比较后的总结、模拟重现与深挖
从驱动层来看,发送过去的内容或指令应该是一样的。只是Server端对于类似于如下的binding,分别有精度损失或没有精度损失:
$1=<('48.1'::double precision)>,type=FLOAT8)
我们看看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
这个参数调整用于文本输出浮点值的位数,包括float4,float8以及几何数据类型。
如果值为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/