PostgreSQL码农集散地

PolarDB提速200倍,10行PL/Python实现DuckDB代理

参考文档点击文末 阅读原文 打开; 推荐 《最好的PostgreSQL学习镜像 》 ;

性能爽翻了, 在PolarDB plpython中使用DuckDB

为什么要在PolarDB PostgreSQL中使用duckdb? 还不是因为PG 在复杂分析查询方面优化器做得太慢了, 弱爆了. 已经写过很多对比文章, 不再细说.

《PG被DuckDB碾压,该反省哪些方面? DuckDB v0.10.3 在Macmini 2023款上的tpch性能表现如何? PostgreSQL使用duckdb_fdw 的tpch加速性能表现如何?》

为什么现在要来说这个? PostgreSQL不是早就支持plpython存储过程了么, 还不是因为要打比赛, 给直接来个上次问AI得到的核爆级弯道超车思路.

《qwen2.5-coder真牛~~用它打PolarDB国赛劲劲的》 AI给了思路和DEMO代码, 我就试试到底行不行, 有多少提升? 结果嘛不用说, 不同的SQL提升各不相同, 拿Q17来说, 可能提升上百倍性能. 分享DEMO之前, 先发个本周五北京的数据库线下活动, 欢迎 扫码 报名 参加.


DEMO

我假设你已经按下面的指南部署好了环境, 在容器内操作.

首先需要把一些依赖包放到你的代码包里, 后面你需要把安装过程写入build.sh脚本内

https://pypi.org/project/setuptools/

cd /tmp  
curl https://files.pythonhosted.org/packages/11/0a/7f13ef5cd932a107cd4c0f3ebc9d831d9b78e1a0e8c98a098ca17b1d7d97/setuptools-41.6.0.zip -o ./setuptools-41.6.0.zip  
  
sudo apt-get install -y unzip  
unzip setuptools-41.6.0.zip   
tar -zcvf setuptools-41.6.0.tar.gz setuptools-41.6.0  
cd setuptools-41.6.0  
sudo python3 setup.py install  

https://pypi.org/project/pip/

cd /tmp  
curl https://files.pythonhosted.org/packages/ce/ea/9b445176a65ae4ba22dce1d93e4b5fe182f953df71a145f557cffaffc1bf/pip-19.3.1.tar.gz -o ./pip-19.3.1.tar.gz  
  
tar -zxvf pip-19.3.1.tar.gz   
cd pip-19.3.1  
sudo python3 setup.py install  

https://pypi.org/project/duckdb/

duckdb包请根据你的开发环境选择, 我用的是m2 芯片mac, 下载arm版本. 测试机可能是x86, 得选另一个.

# curl https://files.pythonhosted.org/packages/e9/35/3fa9b1bbbf4e91e680abfb4c2a2560802698b43c6720096aae0c2d6ef7eb/duckdb-1.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl -o ./duckdb-1.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl  
  
cd /tmp  
curl https://files.pythonhosted.org/packages/37/7c/cf0a0dd56e84570b94927a522698c49fd173b38074ab41a9eb044127b3b3/duckdb-1.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl -o ./duckdb-1.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl  
  
sudo pip3.8 install ./duckdb-1.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl  

现在你可以创建一个plpython3u函数, 在里面可以import duckdb了.

postgres=# create extension plpython3u ;  
  
-- 创建或替换函数    
CREATE OR REPLACE FUNCTION duckdb_proxy(query_text TEXT) RETURNS SETOF RECORD AS $$    
import duckdb    
    
# 连接到 DuckDB 数据库    
con = duckdb.connect(database=':memory:')    
    
# 执行查询    
result = con.execute(query_text)    
    
# 获取结果并返回    
for row in result.fetchall():    
    yield tuple(row)    
    
# 关闭连接    
con.close()    
$$ LANGUAGE plpython3u;    

下面在python shell中把data目录中的csv文件都转换成parquet文件, 可以大幅提升性能. 类型请参考dss.sql文件进行定义.

$ python3.8   
  
import duckdb   
  
duckdb.sql("""copy (select * from   
  read_csv('/data/customer.tbl',  
    delim = '|',  
    header = false,  
    columns = {  
      ' ': 'INTEGER',  
      'c_name': 'VARCHAR(25)',  
      'c_address': 'VARCHAR(40)',  
      'c_nationkey': 'INTEGER',  
      'c_phone': 'CHAR(15)',  
      'c_acctbal': 'DECIMAL(15,2)',  
      'c_mktsegment': 'CHAR(10)',  
      'c_comment': 'VARCHAR(117)'  
    }  
  )  
) to '/data/customer.parquet'""")  
  
  
duckdb.sql("""copy (select * from   
  read_csv('/data/nation.tbl',  
    delim = '|',  
    header = false,  
    columns = {  
      'n_nationkey': 'INTEGER',  
      'n_name': 'CHAR(25)',  
      'n_regionkey': 'INTEGER',  
      'n_comment': 'VARCHAR(152)'  
    }  
  )  
) to '/data/nation.parquet'""")   
  
  
duckdb.sql("""copy (select * from   
  read_csv('/data/lineitem.tbl',  
    delim = '|',  
    header = false,  
    columns = {  
      'l_orderkey': 'INTEGER',  
      'l_partkey': 'INTEGER',  
      'l_suppkey': 'INTEGER',  
      'l_linenumber': 'INTEGER',  
      'l_quantity': 'DECIMAL(15,2)',  
      'l_extendedprice': 'DECIMAL(15,2)',  
      'l_discount': 'DECIMAL(15,2)',  
      'l_tax': 'DECIMAL(15,2)',  
      'l_returnflag': 'CHAR(1)',  
      'l_linestatus': 'CHAR(1)',  
      'l_shipdate': 'DATE',  
      'l_commitdate': 'DATE',  
      'l_receiptdate': 'DATE',  
      'l_shipinstruct': 'CHAR(25)',  
      'l_shipmode': 'CHAR(10)',  
      'l_comment': 'VARCHAR(44)'  
    }  
  )  
) to '/data/lineitem.parquet'""")   
  
  
duckdb.sql("""copy (select * from   
  read_csv('/data/orders.tbl',  
    delim = '|',  
    header = false,  
    columns = {  
      'o_orderkey': 'INTEGER',  
      'o_custkey': 'INTEGER',  
      'o_orderstatus': 'CHAR(1)',  
      'o_totalprice': 'DECIMAL(15,2)',  
      'o_orderdate': 'DATE',  
      'o_orderpriority': 'CHAR(15)',  
      'o_clerk': 'CHAR(15)',  
      'o_shippriority': 'INTEGER',  
      'o_comment': 'VARCHAR(79)'  
    }  
  )  
) to '/data/orders.parquet'""")   
  
  
duckdb.sql("""copy (select * from   
  read_csv('/data/part.tbl',  
    delim = '|',  
    header = false,  
    columns = {  
      'p_partkey': 'INTEGER',  
      'p_name': 'VARCHAR(55)',  
      'p_mfgr': 'CHAR(25)',  
      'p_brand': 'CHAR(10)',  
      'p_type': 'VARCHAR(25)',  
      'p_size': 'INTEGER',  
      'p_container': 'CHAR(10)',  
      'p_retailprice': 'DECIMAL(15,2)',  
      'p_comment': 'VARCHAR(23)'  
    }  
  )  
) to '/data/part.parquet'""")   
  
  
duckdb.sql("""copy (select * from   
  read_csv('/data/partsupp.tbl',  
    delim = '|',  
    header = false,  
    columns = {  
      'ps_partkey': 'INTEGER',  
      'ps_suppkey': 'INTEGER',  
      'ps_availqty': 'INTEGER',  
      'ps_supplycost': 'DECIMAL(15,2)',  
      'ps_comment': 'VARCHAR(199)'  
    }  
  )  
) to '/data/partsupp.parquet'""")   
  
  
duckdb.sql("""copy (select * from   
  read_csv('/data/region.tbl',  
    delim = '|',  
    header = false,  
    columns = {  
      'r_regionkey': 'INTEGER',  
      'r_name': 'CHAR(25)',  
      'r_comment': 'VARCHAR(152)'  
    }  
  )  
) to '/data/region.parquet'""")   
  
  
duckdb.sql("""copy (select * from   
  read_csv('/data/supplier.tbl',  
    delim = '|',  
    header = false,  
    columns = {  
      's_suppkey': 'INTEGER',  
      's_name': 'CHAR(55)',  
      's_address': 'VARCHAR(40)',  
      's_nationkey': 'INTEGER',  
      's_phone': 'CHAR(15)',  
      's_acctbal': 'DECIMAL(15,2)',  
      's_comment': 'VARCHAR(101)'  
    }  
  )  
) to '/data/supplier.parquet'""")   

抽查一下转换后的parquet文件数据是否正常

>>> duckdb.sql("select * from read_parquet('/data/customer.parquet') limit 10")  
┌───────────┬────────────────────┬──────────────────────┬─────────────┬─────────────────┬───────────────┬──────────────┬─────────────────────────────────────────────────────────────────────────────────┐  
│ c_custkey │       c_name       │      c_address       │ c_nationkey │     c_phone     │   c_acctbal   │ c_mktsegment │                                    c_comment                                    │  
│   int32   │      varchar       │       varchar        │    int32    │     varchar     │ decimal(15,2) │   varchar    │                                     varchar                                     │  
├───────────┼────────────────────┼──────────────────────┼─────────────┼─────────────────┼───────────────┼──────────────┼─────────────────────────────────────────────────────────────────────────────────┤  
│         1 │ Customer#000000001 │ IVhzIApeRb ot,c,E    │          15 │ 25-989-741-2988 │        711.56 │ BUILDING     │ to the even, regular platelets. regular, ironic epitaphs nag e                  │  
│         2 │ Customer#000000002 │ XSTf4,NCwDVaWNe6tE…  │          13 │ 23-768-687-3665 │        121.65 │ AUTOMOBILE   │ l accounts. blithely ironic theodolites integrate boldly: caref                 │  
│         3 │ Customer#000000003 │ MG9kdTD2WBHm         │           1 │ 11-719-748-3364 │       7498.12 │ AUTOMOBILE   │  deposits eat slyly ironic, even instructions. express foxes detect slyly. bl…  │  
│         4 │ Customer#000000004 │ XxVSJsLAGtn          │           4 │ 14-128-190-5944 │       2866.83 │ MACHINERY    │  requests. final, regular ideas sleep final accou                               │  
│         5 │ Customer#000000005 │ KvpyuHCplrB84WgAiG…  │           3 │ 13-750-942-6364 │        794.47 │ HOUSEHOLD    │ n accounts will have to unwind. foxes cajole accor                              │  
│         6 │ Customer#000000006 │ sKZz0CsnMD7mp4Xd0Y…  │          20 │ 30-114-968-4951 │       7638.57 │ AUTOMOBILE   │ tions. even deposits boost according to the slyly bold packages. final accoun…  │  
│         7 │ Customer#000000007 │ TcGe5gaZNgVePxU5kR…  │          18 │ 28-190-982-9759 │       9561.95 │ AUTOMOBILE   │ ainst the ironic, express theodolites. express, even pinto beans among the exp  │  
│         8 │ Customer#000000008 │ I0B10bB0AymmC, 0Pr…  │          17 │ 27-147-574-9335 │       6819.74 │ BUILDING     │ among the slyly regular theodolites kindle blithely courts. carefully even th…  │  
│         9 │ Customer#000000009 │ xKiAFTjUsCuxfeleNq…  │           8 │ 18-338-906-3675 │       8324.07 │ FURNITURE    │ r theodolites according to the requests wake thinly excuses: pending requests…  │  
│        10 │ Customer#000000010 │ 6LrEaV6KR6PLVcgl2A…  │           5 │ 15-741-346-9870 │       2753.54 │ HOUSEHOLD    │ es regular deposits haggle. fur                                                 │  
├───────────┴────────────────────┴──────────────────────┴─────────────┴─────────────────┴───────────────┴──────────────┴─────────────────────────────────────────────────────────────────────────────────┤  
│ 10 rows                                                                                                                                                                                      8 columns │  
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘  
  
  
>>> duckdb.sql("select count(*) from read_parquet('/data/customer.parquet')")    
┌──────────────┐  
│ count_star() │  
│    int64     │  
├──────────────┤  
│        15000 │  
└──────────────┘  
为了在PostgreSQL中调用方便, 可以把这8个parquet文件创建为对应的视图, 并把视图保留到duckdb数据文件 /data/duckdb_proxy.db 中
con = duckdb.connect(database='/data/duckdb_proxy.db')    
  
con.execute("""  
CREATE view customer as select * from '/data/customer.parquet';  
CREATE view lineitem as select * from '/data/lineitem.parquet';  
CREATE view nation as select * from '/data/nation.parquet';  
CREATE view orders as select * from '/data/orders.parquet';  
CREATE view part as select * from '/data/part.parquet';  
CREATE view partsupp as select * from '/data/partsupp.parquet';  
CREATE view region as select * from '/data/region.parquet';  
CREATE view supplier as select * from '/data/supplier.parquet';  
""")    
  
con.checkpoint()  

现在可以直接查询这些视图, 相当于查的后面的parquet文件.

con.sql('select count(*) from customer')  
con.sql('select count(*) from lineitem')  
con.sql('select count(*) from nation')  
con.sql('select count(*) from orders')  
con.sql('select count(*) from part')  
con.sql('select count(*) from partsupp')  
con.sql('select count(*) from region')  
con.sql('select count(*) from supplier')  

找个tpch query来试一下, 性能杠杠的

con.sql("""  
select  
 l_returnflag,  
 l_linestatus,  
 sum(l_quantity) as sum_qty,  
 sum(l_extendedprice) as sum_base_price,  
 sum(l_extendedprice * (1 - l_discount)) as sum_disc_price,  
 sum(l_extendedprice * (1 - l_discount) * (1 + l_tax)) as sum_charge,  
 avg(l_quantity) as avg_qty,  
 avg(l_extendedprice) as avg_price,  
 avg(l_discount) as avg_disc,  
 count(*) as count_order  
from  
 lineitem  
where  
 l_shipdate <= date '1998-12-01' - interval '100' day  
group by  
 l_returnflag,  
 l_linestatus  
order by  
 l_returnflag,  
 l_linestatus;""")  
  
  
  
┌──────────────┬──────────────┬───────────────┬────────────────┬─────────────────┬────────────────────┬────────────────────┬────────────────────┬─────────────────────┬─────────────┐  
│ l_returnflag │ l_linestatus │    sum_qty    │ sum_base_price │ sum_disc_price  │     sum_charge     │      avg_qty       │     avg_price      │      avg_disc       │ count_order │  
│   varchar    │   varchar    │ decimal(38,2) │ decimal(38,2)  │  decimal(38,4)  │   decimal(38,6)    │       double       │       double       │       double        │    int64    │  
├──────────────┼──────────────┼───────────────┼────────────────┼─────────────────┼────────────────────┼────────────────────┼────────────────────┼─────────────────────┼─────────────┤  
│ A            │ F            │    3774200.00 │  5320753880.69 │ 5054096266.6828 │  5256751331.449234 │ 25.537587116854997 │  36002.12382901414 │ 0.05014459706340077 │      147790 │  
│ N            │ F            │      95257.00 │   133737795.84 │  127132372.6512 │   132286291.229445 │  25.30066401062417 │  35521.32691633466 │ 0.04939442231075697 │        3765 │  
│ N            │ O            │    7407220.00 │ 10438352681.32 │ 9916049778.5866 │ 10312596801.845673 │ 25.545573370211855 │  35999.16085721873 │ 0.05009328840775139 │      289961 │  
│ R            │ F            │    3785523.00 │  5337950526.47 │ 5071818532.9420 │  5274405503.049367 │   25.5259438574251 │ 35994.029214030925 │ 0.04998927856184382 │      148301 │  
└──────────────┴──────────────┴───────────────┴────────────────┴─────────────────┴────────────────────┴────────────────────┴────────────────────┴─────────────────────┴─────────────┘  

关闭连接

con.close()  

更多的duckdb python api请查看手册

  • https://duckdb.org/docs/api/python/reference/

接下来可以用PostgreSQL函数, 把SQL请求转发给duckdb执行了, 还是用到这个函数, 返回setof record, 这样比较通用, 一个函数就可以转发所有tpch query.

-- 创建或替换函数    
CREATE OR REPLACE FUNCTION duckdb_proxy(query_text TEXT) RETURNS SETOF RECORD AS $$    
import duckdb    
    
# 连接到 DuckDB 数据库    
con = duckdb.connect(database='/data/duckdb_proxy.db')    
    
# 获取结果并返回    
for row in tuple(con.execute(query_text).fetchall()):    
    yield row  
    
# 关闭连接    
con.close()    
$$ LANGUAGE plpython3u;    

用下面的SQL试试这个函数好不好用?

select * from duckdb_proxy($$select count(*) from customer$$) as t(c int8);  
select * from duckdb_proxy($$select count(*) from lineitem$$) as t(c int8);  
select * from duckdb_proxy($$select count(*) from nation$$) as t(c int8);  
select * from duckdb_proxy($$select count(*) from orders$$) as t(c int8);  
select * from duckdb_proxy($$select count(*) from part$$) as t(c int8);  
select * from duckdb_proxy($$select count(*) from partsupp$$) as t(c int8);  
select * from duckdb_proxy($$select count(*) from region$$) as t(c int8);  
select * from duckdb_proxy($$select count(*) from supplier$$) as t(c int8);  

可以了

postgres=# select * from duckdb_proxy($$select count(*) from customer$$) as t(c int8);  
   c     
-------  
 15000  
(1 row)  
  
postgres=# select * from duckdb_proxy($$select count(*) from lineitem$$) as t(c int8);  
   c      
--------  
 600572  
(1 row)  
  
postgres=# select * from duckdb_proxy($$select count(*) from nation$$) as t(c int8);  
 c    
----  
 25  
(1 row)  
  
postgres=# select * from duckdb_proxy($$select count(*) from orders$$) as t(c int8);  
   c      
--------  
 150000  
(1 row)  
  
postgres=# select * from duckdb_proxy($$select count(*) from part$$) as t(c int8);  
   c     
-------  
 20000  
(1 row)  
  
postgres=# select * from duckdb_proxy($$select count(*) from partsupp$$) as t(c int8);  
   c     
-------  
 80000  
(1 row)  
  
postgres=# select * from duckdb_proxy($$select count(*) from region$$) as t(c int8);  
 c   
---  
 5  
(1 row)  
  
postgres=# select * from duckdb_proxy($$select count(*) from supplier$$) as t(c int8);  
  c     
------  
 1000  
(1 row)  

你需要测试tpch SQL的话, 可以使用qgen生成, 方法如下:

cd /tmp/PolarDB-for-PostgreSQL/tpch-dbgen
cp qgen dists.dss queries/
cd queries/

./qgen -h
TPC-H Parameter Substitution (v. 2.14.0 build 0)
Copyright Transaction Processing Performance Council 1994 - 2010
USAGE: ./qgen <options> [ queries ]
Options:
 -a  -- use ANSI semantics.
 -b <str> -- load distributions from <str>
 -c  -- retain comments found in template.
 -d  -- use default substitution values.
 -h  -- print this usage summary.
 -i <str> -- use the contents of file <str> to begin a query.
 -l <str> -- log parameters to <str>.
 -n <str> -- connect to database <str>.
 -N  -- use default rowcounts and ignore :n directive.
 -o <str> -- set the output file base path to <str>.
 -p <n>  -- use the query permutation for stream <n>
 -r <n>  -- seed the random number generator with <n>
 -s <n>  -- base substitutions on an SF of <n>
 -v  -- verbose.
 -t <str> -- use the contents of file <str> to complete a query
 -x  -- enable SET EXPLAIN in each query.

# 例如 生成q1,q17,q18,q20,q21 . -s 0.1 不需要也可以. 因为我前面使用了scale 0.1生成数据.  
./qgen -s 0.1 -d 1 
./qgen -s 0.1 -d 17 
./qgen -s 0.1 -d 18 
./qgen -s 0.1 -d 20 
./qgen -s 0.1 -d 21 

接下来你可能要问, 每次都要定义result table的结构, 字段名可以随便取, 但是我怎么知道返回的每个字段是什么类型呢?  两种方法:

1, 使用duckdb跑一遍, 看看duckdb返回了什么类型,

con.sql("""  
select  
 l_returnflag,  
 l_linestatus,  
 sum(l_quantity) as sum_qty,  
 sum(l_extendedprice) as sum_base_price,  
 sum(l_extendedprice * (1 - l_discount)) as sum_disc_price,  
 sum(l_extendedprice * (1 - l_discount) * (1 + l_tax)) as sum_charge,  
 avg(l_quantity) as avg_qty,  
 avg(l_extendedprice) as avg_price,  
 avg(l_discount) as avg_disc,  
 count(*) as count_order  
from  
 lineitem  
where  
    l_shipdate <= ( date '1998-12-01' - interval '100' day )  
group by  
 l_returnflag,  
 l_linestatus  
order by  
 l_returnflag,  
 l_linestatus  
""")  

例如第一条前面已经跑了, 从结果中照抄, 但是一定要注意, 不要随便用float8, 除非在PG中本来也是返回float8, 否则建议改成decimal, 保证精度

select * from duckdb_proxy($$  
select  
 l_returnflag,  
 l_linestatus,  
 sum(l_quantity) as sum_qty,  
 sum(l_extendedprice) as sum_base_price,  
 sum(l_extendedprice * (1 - l_discount)) as sum_disc_price,  
 sum(l_extendedprice * (1 - l_discount) * (1 + l_tax)) as sum_charge,  
 avg(l_quantity) as avg_qty,  
 avg(l_extendedprice) as avg_price,  
 avg(l_discount) as avg_disc,  
 count(*) as count_order  
from  
 lineitem  
where  
    l_shipdate <= ( date '1998-12-01' - interval '100' day )  
group by  
 l_returnflag,  
 l_linestatus  
order by  
 l_returnflag,  
 l_linestatus  
$$) as t (  
l_returnflag varchar,  
l_linestatus varchar,  
sum_qty decimal(38,2),  
sum_base_price decimal(38,2),  
sum_disc_price decimal(38,4),  
sum_charge decimal(38,6),  
avg_qty decimal,  -- 不要使用float8, 结果可能不正确  
avg_price decimal,  -- 不要使用float8, 结果可能不正确  
avg_disc decimal,  -- 不要使用float8, 结果可能不正确  
count_order int8  
);    

2, 方法2是使用pg_typeof函数, 可以知道返回的是什么类型, 但是要使用PG本地表跑一遍, 例如

postgres=# select  
pg_typeof(sum(l_extendedprice) / 7.0) as avg_yearly   
from  
lineitem,  
part  
where  
    p_partkey = l_partkey  
and p_brand = 'Brand#13'  
and p_container = 'JUMBO PKG'  
and l_quantity < (  
select  
0.2 * avg(l_quantity)  
from  
lineitem  
where  
            l_partkey = p_partkey  
);  
 avg_yearly   
------------  
 numeric  
(1 row)  

tpch 的q17在PG里面跑是比较慢的对吧?

select  
 sum(l_extendedprice) / 7.0 as avg_yearly  
from  
 lineitem,  
 part  
where  
    p_partkey = l_partkey  
 and p_brand = 'Brand#13'  
 and p_container = 'JUMBO PKG'  
 and l_quantity < (  
  select  
   0.2 * avg(l_quantity)  
  from  
   lineitem  
  where  
            l_partkey = p_partkey  
 );  
  
     avg_yearly       
--------------------  
 28135.621428571429  -- 小数点12位  
(1 row)  
  
Time: 11898.286 ms (00:11.898)  

在duckdb中小意思

con.sql("""  
select  
 sum(l_extendedprice) / 7.0 as avg_yearly  
from  
 lineitem,  
 part  
where  
    p_partkey = l_partkey  
 and p_brand = 'Brand#13'  
 and p_container = 'JUMBO PKG'  
 and l_quantity < (  
  select  
   0.2 * avg(l_quantity)  
  from  
   lineitem  
  where  
            l_partkey = p_partkey  
 );  
""")  
  
  
┌───────────────────┐  
│    avg_yearly     │  
│      double       │  
├───────────────────┤  
│ 28135.62142857143 │  -- 小数点11位  
└───────────────────┘  

注意上面这个精度问题, 实际是是PG返回的numeric, 而duckdb返回的是float8.

PG有个相关参数extra_float_digits, 可以控制浮点精度, 但是duckdb貌似没有, 可能需要改duckdb代码?  -- 这里就不细说了.

q17 改写成代理SQL如下, 性能直接提升200多倍.

select * from duckdb_proxy($$  
select  
 sum(l_extendedprice) / 7.0 as avg_yearly  
from  
 lineitem,  
 part  
where  
    p_partkey = l_partkey  
 and p_brand = 'Brand#13'  
 and p_container = 'JUMBO PKG'  
 and l_quantity < (  
  select  
   0.2 * avg(l_quantity)  
  from  
   lineitem  
  where  
            l_partkey = p_partkey  
 )  
$$) as t (  
avg_yearly decimal    -- 不要使用float8, 结果可能不正确       
);    
  
    avg_yearly       
-------------------  
 28135.62142857143  -- 因为duckdb使用了float8, 所以结果并没有fix     
(1 row)  
  
Time: 56.766 ms  

小结

上面这个例子使用plpython3u 写了一个通用函数, 使用这个函数可以将PostgreSQL SQL请求转发给duckdb执行, 加速效果非常明显.

例子中还将csv文件转换为parquet文件, duckdb查询的实际上是parquet文件. 实际上还可以使用duckdb postgres_scanner/csv_reader, 不需要将数据导出到parquet, 对复杂查询性能也有提升, 本文不再赘述. 有兴趣的同学可以试试.

为了方案的完整性和通用性, 建议还要考虑

  • 如何“自动的/方便用户有选择的”将数据同步转换为parquet?
  • 结合query rewrite, 让优化器来决定什么时候使用duckdb? 什么时候使用PostgreSQL/PolarDB heap table? 例如基于代价优化选择?

duckdb本身的优化本文没有提出, 有兴趣的同学也可以关注 https://github.com/digoal/blog 中的相关文章。

最后就是对duckdb为什么更快的原理要能说得明白,能嫁接到polardb吗?怎么和其他队在成绩上打开差异?

参考

https://duckdb.org/docs/api/python/reference/

《PG被DuckDB碾压,该反省哪些方面? DuckDB v0.10.3 在Macmini 2023款上的tpch性能表现如何? PostgreSQL使用duckdb_fdw 的tpch加速性能表现如何?》 《qwen2.5-coder真牛~~用它打PolarDB国赛劲劲的》 《PolarDB数据库创新设计国赛 - 初赛提交作品指南》

这篇文章也给了一些思路.

今日荐书

彩蛋: 国产数据库周边生态

当然一款数据库要流行起来, 除了自己要强大, 还离不开生态. 用好周边生态工具, 管理水平战胜90%老司机!!! 下面简单介绍一下国产数据库周边生态.

1、管控软件

鸣嵩 (前阿里云数据库总经理 / 研究员)等大佬们创业创办的云猿生, 核心产品是 KubeBlocks . 他们的理念是让管理数据库和搭积木一样简单, 如果你要管理很多套并且种类(OLTP\OLAP\NoSQL\KV\TS\MQ等)很多的数据库产品, 推荐首选.
  • https://github.com/apecloud/kubeblocks
PG中文社区核心委员 唐成 老师的公司乘数开源的 Clup , 专用管理PostgreSQL和PolarDB的集群管理软件, 如果你要管理很多套数据库, 推荐选择. 并且Clup还提供了企业版、自研的连接池、分布式存储、一体机、备份平台等, 是企业用户推荐之选.
  • https://www.csudata.com/
若航 开源的 pigsty , 集成了300多个PG插件的PG集群和PolarDB集群管理软件, 如果你要管理很多套PG或PolarDB数据库, 且对插件有特别多的需求, 推荐选择.
  • https://pigsty.cc/zh/

2、审计监控诊断优化

翟总 (曾经是我背后的男人)到海信聚好看后研发的 DBdoctor , 采用ebpf技术, 在对数据库几乎没有影响的情况下实时监控数据库和服务器的各项指标, 发现和诊断问题根因非常方便.
  • https://www.dbdoctor.cn/
天舟 老哥的核心产品 Bytebase 是位于您和数据库之间的中间件。它是数据库 DevOps 的 GitLab/GitHub,专为开发人员、DBA 和平台工程师打造。
  • https://bytebase.cc/docs/introduction/what-is-bytebase/
PawSQL , SQL优化和诊断产品. D-Smart , Oracle老前辈 白老大 出品, 专注企业级市场, 将业界顶级DBA经验的产品化作品, 产品功能包括数据库监控、诊断、优化等.
  • https://www.modb.pro/db/567140

3、国产数据库IDE

IDE是开发者的必备工具,例如社区有pgAdmin, 国产IDE则可以看看老程序猿 达刚 老师的 DeskUI :
  • https://www.deskui.com

4、数据同步&迁移&备份恢复

NineData , 老领导出去创业做的产品, 产品涵盖了数据同步、迁移、备份、比对、devops、chatDBA等.
  • https://www.ninedata.cloud/home
DSG , 非常老牌的数据库同步迁移企业级产品, 支持各种数据库的异构和同构迁移, 用他们的话说, 没有dsg搞不定的迁移, 比goldengate还牛.
  • https://www.dsgdata.com/

公开课

如果你对PolarDB学习感兴趣可以阅读这个公开课系列:


除了PolarDB还非常值得关注的几款PG栈国产数据库:

  • HaloDB (基于PG兼容PostgreSQL、Oracle、MySQL. http://www.halodbtech.com/ )、
  • IvorySQL (基于开源PG兼容PG、Oracle. https://www.ivorysql.org/zh-cn/ )、
  • ProtonBase (云原生分布式数仓. https://protonbase.com/ )、
  • 成都 文武 数据库(https://ww-it.cn)

参考文档点击 阅读原文 获得
感谢关注我的github (https://github.com/digoal/blog) 及视频号: