Ошибка PostgreSQL: аргумент строки запроса EXECUTE равен нулю

#c# #postgresql #plpgsql #npgsql #postgresql-9.3

#c# #postgresql #plpgsql #npgsql #postgresql-9.3

Вопрос:

У меня есть таблица с именем 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#Appsgithub.npgsql.Npgsql.stocksrcNpgsqlNpgsqlState.cs:line890 at Npgsql.ForwardsOnlyDataReader.GetNextResponseObject() in c:C#Appsgithub.npgsql.Npgsql.stocksrcNpgsqlNpgsqlDataReader.cs:line 1175 at 
    Npgsql.ForwardsOnlyDataReader.GetNextRowDescription() in c:C#Appsgithub.npgsql.Npgsql.stocksrcNpgsqlNpgsqlDataReader.cs:line 1191    at 
    Npgsql.ForwardsOnlyDataReader.NextResult() in c:C#Appsgithub.npgsql.Npgsql.stocksrcNpgsqlNpgsqlDataReader.cs:line 1377    at 
    Npgsql.NpgsqlCommand.ExecuteNonQuery() in c:C#Appsgithub.npgsql.Npgsql.stocksrcNpgsqlNpgsqlCommand.cs:line523
 

Моя система имеет функцию автоматического повтора, и в конечном итоге каждая запись вставляется в базу данных, но после многих исключений, когда нагрузка высока.

База данных — PostgreSQL 9.3 на сервере CentOS 6, а клиент — C # .NET с использованием драйвера Npgsql.

Таблица:

 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();
}
 

Почему эта странная ошибка появляется только тогда, когда система находится под нагрузкой? Что я могу сделать, чтобы этого не произошло?

Спасибо!

Комментарии:

1. Я даже установил точку останова и отладил ее и наблюдал, как переменная _query устанавливается на ДЕЙСТВИТЕЛЬНЫЙ запрос перед сбоем EXECUTE _query , но если я вручную выполню значение _query после этого, это сработает ?!

2. Интересно. Проблема с параллелизмом / потоками, возможно, в nPgSQL, возможно, в приложении?

3. Это то, что я подумал, хотя ошибки также появляются в файле журнала PostgreSQL, и я установил блокировку вокруг кода вставки, и он по-прежнему выдает ту же ошибку: (

4. Вы пытались отладить его с RAISE LOG помощью? Может быть проще сопоставить их с ошибками в журнале PostgreSQL. Также: вы установили внешний ключ на evidence.notification_id notifications.id ?

5. Внешнего ключа нет, он полностью де-нормализован. Не нужно поднимать ЖУРНАЛ, поскольку я прошел через него с помощью отладчика — в запросе нет ничего плохого, его можно запустить впоследствии. Интересно, что я также смог воспроизвести это, введя данные вручную с помощью pgAdmin!

Ответ №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_id есть NULL .
  2. notifications Для данного нет строки NEW.notification_id .

Попробуйте эту модифицированную триггерную функцию для отладки:

 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) . Просто используйте text или varchar .
    • Избегайте слишком большого количества назначений там, где это не нужно — сравнительно дорого в PL / pgSQL.
  • Добавьте a RAISE , чтобы проверить мою гипотезу.
    Если вы получите сообщение об ошибке, следующим шагом будет определение двух возможных причин. Должно быть тривиальным…

Комментарии:

1. Вы были правы, уведомление поступало после подтверждения, поэтому оно было нулевым. Когда я его отлаживал, очевидно, что он давал время для создания уведомления, поэтому он работал каждый раз. Я добавил raised_local_time к аргументам, тем самым удалив зависимость от уведомлений, что означало, что порядок не важен. Спасибо за понимание!