【问题标题】:PostgreSQL error: query string argument of EXECUTE is nullPostgreSQL 错误:EXECUTE 的查询字符串参数为空
【发布时间】:2014-06-21 23:14:39
【问题描述】:

我有一个名为evidence 的表,它带有一个触发器,该触发器调用一个存储过程,该存储过程基本上按月进行表分区。但是,当我开始在负载下插入大​​量行时,我得到一个模糊的错误:

Npgsql.NpgsqlException: query string argument of EXECUTE is null 
Severity: ERROR Code: 22004 at Npgsql.NpgsqlState.<ProcessBackendResponses_Ver_3>d__a.MoveNext() in c:\C#Apps\github.npgsql.Npgsql.stock\src\Npgsql\NpgsqlState.cs:line890 at Npgsql.ForwardsOnlyDataReader.GetNextResponseObject() in c:\C#Apps\github.npgsql.Npgsql.stock\src\Npgsql\NpgsqlDataReader.cs:line 1175 at 
    Npgsql.ForwardsOnlyDataReader.GetNextRowDescription() in c:\C#Apps\github.npgsql.Npgsql.stock\src\Npgsql\NpgsqlDataReader.cs:line 1191    at 
    Npgsql.ForwardsOnlyDataReader.NextResult() in c:\C#Apps\github.npgsql.Npgsql.stock\src\Npgsql\NpgsqlDataReader.cs:line 1377    at 
    Npgsql.NpgsqlCommand.ExecuteNonQuery() in c:\C#Apps\github.npgsql.Npgsql.stock\src\Npgsql\NpgsqlCommand.cs:line523

我的系统具有自动重试功能,最终每条记录都会插入数据库,但在负载高时会出现许多异常。

数据库是 CentOS 6 服务器上的 PostgreSQL 9.3,客户端是使用 Npgsql 驱动程序的 C# .NET。

表格:

CREATE TABLE evidence
(
  id uuid NOT NULL,
  notification_id uuid NOT NULL,
  feedback character varying(200),
  result character varying(20),
  trigger_action_type character varying(200),
  trigger_action_id uuid,
  data_type integer NOT NULL,
  data bytea,
  name character varying(30),
  CONSTRAINT pk_evidence PRIMARY KEY (id)
);

触发器:

CREATE TRIGGER evidence_move_to_partition_tables
  BEFORE INSERT
  ON evidence
  FOR EACH ROW
  EXECUTE PROCEDURE partition_evidence_by_month();

触发功能:

CREATE OR REPLACE FUNCTION partition_evidence_by_month()
  RETURNS trigger AS
$BODY$
DECLARE 
    _notification_id uuid;
    _raised_local_time timestamp without time zone;
    _table_name character varying(35);
    _start_date timestamp without time zone;
    _end_date timestamp without time zone;
    _table_space character varying(50) := 'ls_tablespace2';
    _query text;
BEGIN
    _notification_id := NEW.notification_id;
    SELECT raised_local_time FROM notifications WHERE id=_notification_id INTO _raised_local_time;
    _start_date := date_trunc('month', _raised_local_time);
    _end_date   := _start_date + '1 month'::interval;
    _table_name := 'evidence-' || to_char(_start_date, 'YYYY-MM');

    -- check to see if table already exists
    PERFORM 1
    FROM   pg_catalog.pg_class c
    JOIN   pg_catalog.pg_namespace n ON n.oid = c.relnamespace
    WHERE  c.relkind = 'r'
    AND    c.relname = _table_name
    AND    n.nspname = 'public';

    -- if the table doesn't exist, then create it now
    IF NOT FOUND THEN
        -- create partition table
        _query := 'CREATE TABLE public.' || quote_ident(_table_name) || ' ( ) INHERITS (public.evidence)';
        EXECUTE _query;
        -- alter owner
        --EXECUTE 'ALTER TABLE public.' || quote_ident(_table_name) || ' OWNER TO postgres';
        -- add index
        --EXECUTE 'ALTER TABLE public.' || quote_ident(_table_name) || ' ADD PRIMARY KEY (id)';
    END IF;
    -- move the data to the partition table
    EXECUTE 'INSERT INTO public.' || quote_ident(_table_name) || ' VALUES ($1.*)' USING NEW;
    RETURN NULL;
END;
$BODY$ LANGUAGE plpgsql VOLATILE COST 100;

调用代码:

using (var cmd = db.CreateCommand())
{
    cmd.CommandText = @"INSERT INTO evidence   
    (id, notification_id, feedback, result, trigger_action_type, 
     trigger_action_id, data_type, data, name) 
    VALUES (@id,@nid,@feedback,@result,@tat,@taid,@dt,@data,@name)";
    cmd.Parameters.AddWithValue("@id", evItem.ID);
    cmd.Parameters.AddWithValue("@nid", evItem.NotificationID);
    cmd.Parameters.AddWithValue("@feedback", evItem.Feedback);
    cmd.Parameters.AddWithValue("@result", evItem.Result);
    cmd.Parameters.AddWithValue("@tat", evItem.TriggerActionType);
    cmd.Parameters.AddWithValue("@taid", evItem.TriggerActionID);
    cmd.Parameters.AddWithValue("@dt", (int)evItem.DataType);
    cmd.Parameters.AddWithValue("@data", evItem.Data);
    cmd.Parameters.AddWithValue("@name", evItem.Name);
    cmd.ExecuteNonQuery();
}

为什么这个奇怪的错误只有在系统负载时才会出现?我能做些什么来防止它发生?

谢谢!

【问题讨论】:

  • 我什至设置了一个断点并对其进行了调试,并观察了 _query 变量在EXECUTE _query 失败之前被设置为一个有效的查询,但是如果我之后手动运行_query 的值,它可以工作吗?!
  • 有趣。并发/线程问题,可能在 nPgSQL 中,可能在应用程序中?
  • 这就是我的想法,虽然错误也出现在 PostgreSQL 的日志文件中,我在插入代码周围加了一个锁,它仍然抛出同样的错误:(
  • 你试过用RAISE LOG调试它吗? 可能更容易将它们与 PostgreSQL 日志中的错误相匹配。另外:您是否将evidence.notification_id 上的外键设置为notifications.id
  • 没有外键,这是完全反规范化的。不需要 RAISE LOG,因为我已经使用调试器逐步完成了它 - 查询没有任何问题,它可以在之后运行。有趣的是,我也可以通过使用 pgAdmin 手动输入数据来重现这一点!

标签: c# postgresql plpgsql npgsql postgresql-9.3


【解决方案1】:

错误信息是

EXECUTE 的查询字符串参数为空

你有两个EXECUTE 命令:

_query := 'CREATE TABLE public.'
        || quote_ident(_table_name) || ' ( ) INHERITS (public.evidence)';
EXECUTE _query;

...

EXECUTE 'INSERT INTO public.'
      || quote_ident(_table_name) || ' VALUES ($1.*)' USING NEW;

唯一可以是NULL 的部分是table_name
table_name 成为 NULL 的唯一机会就在这里:

SELECT raised_local_time FROM notifications WHERE id=_notification_id
INTO _raised_local_time;

所以原因一定是两个原因之一

  1. NEW.notification_idNULL

  2. 对于给定的NEW.notification_idnotifications 中没有行。

试试这个用于调试的修改触发函数
CREATE OR REPLACE FUNCTION partition_evidence_by_month()
  RETURNS trigger AS
$func$
DECLARE 
   _table_name text;
BEGIN
   SELECT 'evidence-' || to_char(raised_local_time, 'YYYY-MM')
   FROM   public.notifications -- schema-qualify to be sure
   WHERE  id = NEW.notification_id
   INTO   _table_name;

   IF _table_name IS NULL THEN
      RAISE EXCEPTION '_table_name is NULL. Should not occur!';
   END IF;

   IF NOT EXISTS (   -- create table if it does not exist
      SELECT 1
      FROM   pg_catalog.pg_class c
      JOIN   pg_catalog.pg_namespace n ON n.oid = c.relnamespace
      WHERE  c.relkind = 'r'
      AND    c.relname = _table_name
      AND    n.nspname = 'public') THEN

      EXECUTE 'CREATE TABLE public.'
            || quote_ident(_table_name) || ' ( ) INHERITS (public.evidence)';
   END IF;

   EXECUTE 'INSERT INTO public.'
         || quote_ident(_table_name) || ' VALUES $1'  -- Use NEW row directly
   USING  NEW;       -- write data to the partition table

   RETURN NULL;
END
$func$ LANGUAGE plpgsql;
  • 删除未使用的变量并简化代码。 (这显然是一个简化的例子。)

    • 除此之外,您根本不需要date_trunc()。只需将原始时间戳提供给to_char()

    • 使用varchar(n) 没有意义。只需使用textvarchar

    • 在不必要的地方避免过多的赋值 - 在 PL/pgSQL 中相对昂贵。

  • 添加RAISE 来检查我的假设。
    如果您收到错误消息,下一步将是区分这两种可能的原因。应该是微不足道的......

【讨论】:

  • 你是对的,通知是在证据之后进来的,所以它是空的。当我调试它时,显然它给了创建通知的时间,这就是它每次都能工作的原因。我在参数中添加了 raise_local_time,从而消除了对通知的依赖,这意味着顺序并不重要。感谢您的洞察力!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-02-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-04
  • 2011-02-10
  • 2015-02-02
相关资源
最近更新 更多