#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;
Таким образом, причиной должна быть одна из двух причин:
NEW.notification_id
естьNULL
.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 к аргументам, тем самым удалив зависимость от уведомлений, что означало, что порядок не важен. Спасибо за понимание!