postgresql - 用于编码字符串的 UDF 在 psql 和 Perl 中有效,但在 Python 中无效

标签 postgresql sql-insert upsert postgresql-9.4 udf

我在 Postgres 9.4 中编写了一个用户定义的函数来编码字符串:

CREATE OR REPLACE FUNCTION platform.encode_sig(sig text)   
RETURNS bigint AS $BODY$ 
  declare   sig_id bigint; 
begin
    lock table platform.sig2encodings in access exclusive mode;   
    execute 'select sig_id from platform.sig2encodings where sig = ''' || sig || '''' into sig_id;

    if sig_id is null   
    then
       raise notice 'I do not have encoding for %', sig;
       execute 'insert into platform.sig2encodings (sig) values (''' || sig || ''')';
       execute 'select sig_id from platform.sig2encodings where sig = ''' || sig || '''' into sig_id;   
    else
       raise notice 'I do have encoding for %', sig;   
    end if;

  return sig_id;

END; 
$BODY$   
LANGUAGE plpgsql VOLATILE   COST 100;

表格:

CREATE TABLE platform.sig2encodings
(
  sig_id bigserial NOT NULL,
  sig text,
  CONSTRAINT sig2encodings_pkey PRIMARY KEY (sig_id ),
  CONSTRAINT sig2encodings_sig_key UNIQUE (sig )
)

pgadmin 或 psql 中的调用将数据插入表中:

select * from platform.encode_sig('NM_Gateway_NL_Shutdown');

python 中的调用获取id,但插入数据:

db="""dbname='XXX' user='XXX' password='XXX' host=XXX port=XXX"""

def encode_sig(sig):
   try:
      conn=psycopg2.connect(db)
   except:
      print "I am unable to connect to the database."
      exit()

   cur = conn.cursor()
   try:
      sql = "select * from platform.encode_sig('" + sig + "');"
      print sql
      cur.execute(sql)
   except:
      print "I can't retrieve sid"

   row = cur.fetchone()
   return row[0]

print str(encode_sig('NM_Gateway_UDS_CC'))

python 脚本的输出:

$ ./events_insert.py 
616
617
618
619
620
621
$ ./events_insert.py 
622
623
624
625
626
627

postgres 中的表是空的。这是怎么回事?

更新:

以下 perl 脚本有效(所有控制台输出 (NOTICE) 和表中的行):

#!/usr/bin/perl

use strict;
use warnings;

use Data::Dumper;
use DBI;

my $dbh = get_connection();
$dbh->do("SELECT platform.encode_sig('blah blah blah')");
$dbh->disconnect();

sub get_connection {
    return DBI->connect('dbi:Pg:dbname=XXX;host=XXX;port=XXX',
                        'XXX', 'XXX', { RaiseError => 1 });
}

数据库配置非常标准。这些行来自 postgresql.conf(因为它们被注释掉了,所以采用默认值):

#fsync = on                             # turns forced synchronization on or off
#synchronous_commit = on                # synchronization level;
                                        # off, local, remote_write, or on
#wal_sync_method = fsync                # the default is the first option
                                        # supported by the operating system:
                                        #   open_datasync
                                        #   fdatasync (default on Linux)
                                        #   fsync
                                        #   fsync_writethrough
                                        #   open_sync
#full_page_writes = on                  # recover from partial page writes
#wal_log_hints = off                    # also do full page writes of non-critical updates
                                        # (change requires restart)
#wal_buffers = -1                       # min 32kB, -1 sets based on shared_buffers
                                        # (change requires restart)
#wal_writer_delay = 200ms               # 1-10000 milliseconds

#commit_delay = 0                       # range 0-100000, in microseconds
#commit_siblings = 5                    # range 1-1000

最佳答案

目前还不清楚,在您看到 sig_id 返回后,表怎么会是空的。想到的唯一合理的解释:

  • 您不小心检查了不同的表(在不同的模式或不同的数据库中)。
  • 您正在使用 auto_commit = off 运行并且忘记了 COMMIT 您的事务。在 COMMIT 之前,其他 session 看不到结果。

无论哪种方式,您的函数都是不必要的复杂,您不需要动态 SQLEXECUTE。由于您将未转义的文本参数连接到代码中,因此很容易出现随机语法错误和SQL 注入(inject)
您还很危险地接近参数名称 sig 和列名称 sig 之间的命名冲突。您用动态 SQL 放弃了这最后一颗子弹,但它仍然是一把上了膛的步兵枪。阅读 chapter Variable Substitution对于手册中的 PL/pgSQL,并考虑唯一名称。

最后,每行调用一个函数也是效率极低的。 整个过程可以用这个单个SQL语句代替:

LOCK TABLE platform.sig2encodings IN ACCESS EXCLUSIVE MODE;

WITH sel AS (
   SELECT e.sig_id, e.sig
       , (s.sig IS NULL) AS insert_new
   FROM   platform.encode_sig e
   LEFT   JOIN platform.sig2encodings s USING (sig)
   )
,    ins AS (
   INSERT INTO platform.sig2encodings (sig)
   SELECT sig FROM sel WHERE insert_new
   RETURNING sig_id, sig, true  -- value for insert_new
   )
SELECT * FROM sel WHERE NOT insert_new
UNION ALL
SELECT * FROM ins;

这会将 encode_sig 中的所有 sig 插入到 sig2encodings 中,但它们还不存在。它返回生成的 sig_id, siginsert_new = true,附加到 sig_id, siginsert_new = false encode_sig 未插入。

如果您需要一个可以安全并发使用的单行 INSERT-or-SELECT 函数:

或者您希望 INSERT .. ON CONFLICT IGNORE 使其进入下一个版本以简化事情:

更新:已committed for 9.5 . /devel manual already has instructions .

关于postgresql - 用于编码字符串的 UDF 在 psql 和 Perl 中有效,但在 Python 中无效,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29634081/

相关文章:

ruby-on-rails - 允许 Rails 中的空值的唯一索引

postgresql - 在 Postgresql 中执行更新插入时,ON CONFLICT 子句中未使用部分索引

ruby-on-rails - 具有不同 key 的 Mongoid upsert

postgresql - PostgreSQL UPSERT(可写 CTE)中使用一个表更新另一个表的不明确列

sql - 返回任意类型的 PostgreSQL 函数

postgresql - Kafka Connect JDBC Sink quote.sql.identifiers 不工作

ruby-on-rails - rspec 无法识别 Postgres View

java - Spring Data Rest 发布无法保存嵌套对象

vb.net - 使用 VB.NET 执行存储过程

mysql - 通过 LEFT JOIN 优化 SQL 子查询