将数据存储为数字和双精度

时间:2014-10-15 17:20:16

标签: sql performance postgresql numeric

我有一个带有数字字段的表,比如说:

create table data (
  id bigserial PRIMARY KEY,
  quantity numeric(21,8) NOT NULL
)

我需要一个numeric类型,因为有些查询需要一定程度的准确度,而这些准确度无法从双打中获得。

但我也有一些查询可以累计数百万这些数量,不关心舍入问题,需要尽可能快。

是否有标准策略可以执行此操作,或者我应该只复制每个数字:

create table data (
  id bigserial PRIMARY KEY,
  quantity_exact numeric(21,8) NOT NULL,
  quantity double precision NOT NULL
)

2 个答案:

答案 0 :(得分:2)

阅读以下更新以获取完整视图。

让我们比较一下你概述的两种情况:

  1. 只使用一列(哪一列 - float8numeric(21,8)?)或
  2. 保留两者(keep two)。
  3. 一些观察结果。

    1. 如果同时保留这两列,我们会说数据重复,这与规范化相矛盾,并引入系统的模糊性,需要特殊处理。这使-1成为keep two

    2. 列的大小为:

      SELECT 'float8'::coltyp, pg_column_size(random()::float8) UNION ALL
      SELECT 'numeric(21,8)',  pg_column_size(random()::numeric(21,8));
      

      在这种情况下保留两列将需要几乎两倍的空间。 -1keep two案例以及+0.5变体float8,因为它的尺寸略小。

    3. 速度测试显示以下内容:

      SET work_mem TO '2000MB'; -- to avoid usage of temp files
      EXPLAIN (analyze,buffers,verbose)
      SELECT ((random()*1234567)::float8 / 2 + 3) * 5
        FROM generate_series(1,(1e7)::int) s;
      EXPLAIN (analyze,buffers,verbose)
      SELECT ((random()*1234567)::numeric(21,8) / 2 + 3) * 5
        FROM generate_series(1,(1e7)::int) s;
      

      在我的i7 2.3GHz MBP上,我得到了(基于5次运行):

      • 3135.238ms
      • 不超过float8
      • 17325.514ms以后不再是numeric(21,8)

      所以我们在+1案例中明确float8。这是一个仅限内存的测试,查询一个表(和一个冷表)将需要更多的时间。

    4. 考虑到您的性能要求,似乎坚持使用float8是一种明显的方法(+1.5 vs -2)。您可以在此表格的顶部创建一个视图,该视图将同时宣传原始float8和已投放numeric(21,8),以满足您对高精度要求的查询。


      更新:a_horse_with_no_name发表评论后,我决定重新测试,这次是使用真实表格。我去了9.4beta3,因为9.4带有非常好的pg_prewarm模块。

      这就是我所做的:

      export PGDATA=$HOME/9.4b3
      initdb -k -E UTF8 -A peer
      pg_ctl start
      

      然后,我使用新的ALTER SYSTEM功能更改了一些默认设置:

      ALTER SYSTEM SET shared_buffers TO '1280MB';
      ALTER SYSTEM SET checkpoint_segments TO '99';
      ALTER SYSTEM SET checkpoint_completion_target TO '0.9';
      

      通过pg_ctl restart重新启动服务器,现在是测试:

      SELECT id::int, 1::int AS const, (random()*1234567)::float8 as val
        INTO f FROM generate_series(1,(1e7)::int) id;
      SELECT id::int, 1::int AS const, (random()*1234567)::numeric(21,8) as val
        INTO n FROM generate_series(1,(1e7)::int) id;
      CREATE EXTENSION pg_prewarm;
      VACUUM ANALYZE;
      SELECT pg_prewarm('f');
      SELECT pg_prewarm('n');
      -- checking table size
      SELECT relname,pg_size_pretty(pg_total_relation_size(oid))
        FROM pg_class WHERE relname IN ('f','n');
      -- checking sped
      EXPLAIN (analyze, buffers, verbose) SELECT min(id), max(id), sum(val) FROM f;
      EXPLAIN (analyze, buffers, verbose) SELECT min(id), max(id), sum(val) FROM n;
      

      现在结果大不相同:

      • 尺寸为422 MB vs 498 MB
      • float8的平均时间为2272.833ms
      • numeric(21,8) 3289.542ms

      现在,这肯定不能反映真实情况,但在我看来:

      • 使用numeric会为关系的大小添加一些东西(对我来说是20%);
      • 在某种程度上使查询变慢(对我而言是44%)。

      说实话,我对这些数字感到非常惊讶。两个表都是完全缓存的,因此只花时间来处理元组并进行数学计算。我认为这是一个更大的区别。

      就个人而言,我现在会选择numeric类型,因为它没有提供如此大的性能差异和数据精度。

答案 1 :(得分:1)

我加5美分。 PG 9.4

CREATE TABLE orders( 
count integer not null
...
cost character varying(15) -- cost as string '10.22' for example
ncost numeric(10,2) -- same cost as numeric 10.22
)

~260000行:

解释分析

select sum(count*ncost) from orders 

“总运行时间:743.259 ms”(10次测试后的热数据) 解释分析

select sum(count*cost::numeric(10,2)) from orders 

“总运行时间:577.289毫秒” 因此,将sum()的成本保持为字符串更快。