Andrei Lepikhov:为什么numeric在PostgreSQL数据库中如此流行?

Andrei Lepikhov:为什么numeric在PostgreSQL数据库中如此流行?

💡 原文英文,约5100词,阅读约需19分钟。
📝

内容提要

文章探讨了金融应用中精确十进制数类型(如NUMERIC/DECIMAL)的必要性。法规要求精确存储和特定舍入规则,二进制浮点无法满足。支付系统将金额作为整数加货币比例传输,而ERP系统因比例多变需用十进制。分析引擎倾向固定宽度整数,性能优化趋势是限制精度至38位。结论是整数适合统一比例场景,十进制适合多变比例,且序列化需确保跨系统一致性。

🔎

延伸解读

法律要求的是行为而非类型

欧盟和英国税务法规并未直接指定使用NUMERIC类型,而是要求精确存储、特定舍入规则(如恰好一半时向上舍入)以及转换过程中不进行中间舍入。这些行为要求使得二进制浮点类型在构造上无法满足,而PostgreSQL的numeric类型恰好能提供这些特性。因此,选择numeric并非出于标准强制,而是为了满足法律规定的行为。

整数与十进制的选择取决于比例尺来源

支付系统(如ISO 8583)将金额作为整数传输,比例尺由货币代码决定,因此整数类型适用。但在ERP系统中,比例尺因字段而异(如金额两位小数、数量三位),且可能动态变化,单一比例尺的整数无法满足需求。因此,当比例尺统一且固定时,整数更高效;当比例尺多变时,十进制类型是必要选择。

序列化一致性常被忽视

除了存储和计算,金额数据在跨系统传输时还需保证解析一致性。整数加外部比例尺(如货币代码或模式定义)是唯一能被所有语言和解析器无歧义处理的构造。ISO 20022等格式明确使用十进制字符串,而支付API则采用整数或字符串,这凸显了序列化层面对精确性的要求,与存储和计算层面的考量同等重要。

性能优化趋势:限制精度至38位

现代数据库引擎(如CedarDB、DuckDB、ClickHouse)普遍将十进制精度上限设为38位(基于int128),并放弃可变长度任意精度,以提升性能。PostgreSQL仍支持高达131072位精度,但这也导致其numeric运算较慢。文章暗示,PostgreSQL可能缺少一种有界宽度的numeric类型,以在保持精确性的同时提高性能。

Q&A

为什么金融应用中推荐使用NUMERIC类型而不是浮点类型?

因为金融应用需要精确存储金额和遵循特定的舍入规则,而二进制浮点数无法精确表示0.01等十进制小数,且舍入行为不确定。法规如欧盟条例要求精确到六位有效数字且禁止舍入或截断,因此需要NUMERIC这类精确十进制类型。

SQL标准中NUMERIC和DECIMAL有什么区别?

SQL标准中,NUMERIC指定精确的精度和标度,而DECIMAL的精度是实现定义的,但必须大于或等于指定的精度。例如,NUMERIC(15,2)限制为15位,而DECIMAL(15,2)表示至少15位。PostgreSQL将两者合并为numeric类型。

支付系统如何表示金额?为什么使用整数?

支付系统(如ISO 8583)将金额作为整数传输,标度由货币代码决定,例如日元为0位小数,美元为2位,巴林第纳尔为3位。这样避免了浮点误差,并确保跨系统解析的一致性。

ERP系统中为什么不能简单地用整数表示金额?

ERP系统中,标度由业务领域决定,不同字段可能需要不同的小数位数(如金额2位、数量3位、汇率更多),且可能动态变化。整数需要固定的标度,无法灵活适应,因此需要DECIMAL/NUMERIC类型。

法规对货币舍入有什么具体要求?

欧盟条例要求转换汇率使用六位有效数字,禁止舍入或截断,且舍入到分时若恰好一半则向上舍入。英国HMRC要求增值税计算到三位小数,少于半便士舍去,半便士及以上进位。这些要求无法用浮点类型满足。

为什么序列化时金额需要表示为整数加标度?

因为序列化需要跨系统一致性,整数是唯一所有语言和解析器都支持的类型,标度可以放在模式、相邻字段或货币代码中。这样确保不同实现解析后得到完全相同的值。

TPC基准测试对金额类型有什么要求?

TPC-C和TPC-E要求金额字段使用精确数值类型,如DECIMAL或NUMERIC,而TPC-H和TPC-DS允许对DECIMAL有1%的容差,仅COUNT需要精确。这表明分析型负载可以放宽精确性要求。

为什么现代数据库倾向于将NUMERIC精度限制在38位?

因为38位可以放入int128,性能更好。许多系统如Snowflake、DuckDB、CedarDB都采用此上限,而PostgreSQL支持任意精度但性能较慢。限制宽度是为了提高运算速度。

🏷️

标签

➡️

继续阅读