Я решил переместить свое приложение-демон C # (используя dotConnect в качестве поставщика ADO.NET) с SQL Server 2008 R2 на PostgreSQL 9.0.4 x64 (на Windows Server 2008 R2). Поэтому я немного изменил все запросы, чтобы они соответствовали синтаксису PostgreSQL, и ... застрял в поведении, которое никогда не происходило с такими же запросами на SQL Server (даже на слабо экспресс-выпуске).
Допустим, база данных содержит 2 очень простые таблицы, не имеющие никакого отношения друг к другу. Они выглядят примерно так: ID, Имя, Модель, ScanDate, Заметки. У меня есть процесс преобразования, который читает данные по TCP / IP, обрабатывает их, запускает транзакцию и помещает результаты в вышеупомянутые 2 таблицы, используя ванильные INSERT. Таблицы изначально пусты; нет BLOB-столбцов. В плохой день существует около 500.000 INSERT, все они заключены в одну транзакцию (кстати, их нельзя разделить на несколько транзакций). Никакие SELECT, ОБНОВЛЕНИЯ или УДАЛЕНИЯ никогда не делаются. Пример INSERT (идентификатор bigserial - автоматически инкрементируется):
INSERT INTO logs."Incoming" ("Name", "Model", "ScanDate", "Notes")
VALUES('Ford', 'Focus', '2011-06-01 14:12:32', NULL)
SQL Server спокойно принимает нагрузку, поддерживая разумный рабочий набор ~ 200 МБ. Однако PostgreSQL занимает дополнительно 30 МБ каждую секунду, когда выполняется транзакция (!), И быстро исчерпывает системную память.
Я сделал свой RTFM и попытался поиграться с postgresql.conf: установив «work_mem» на минимум 64 КБ (это немного замедлило переключение памяти), уменьшив «shared_buffers» / «temp_buffers» до минимума (без разницы) - но безрезультатно. Снижение уровня изоляции транзакции до Read Uncommitted не помогло. Там нет индексов, кроме одного на ID BIGSERIAL (PK). SqlCommand.Prepare () не имеет значения. Параллельные соединения никогда не устанавливаются: демон использует базу данных исключительно.
Может показаться, что PostgreSQL не справляется с ошеломляющим простым INSERT-фестом, в то время как SQL Server может это сделать. Может быть, это разница изоляции блокировок снимков PostgreSQL и SQL Server? Для меня это факт: ванильный SQL Server работает, в то время как ни ванильный, ни измененный PostgreSQL не работают.
Что можно сделать, чтобы потребление памяти в PostgreSQL оставалось плоским (как, очевидно, в случае с SQL Server) во время выполнения транзакции на основе INSERT?
РЕДАКТИРОВАТЬ: Я создал искусственный тестовый случай:
DDL
CREATE TABLE sometable
(
"ID" bigserial NOT NULL,
"Name" character varying(255) NOT NULL,
"Model" character varying(255) NOT NULL,
"ScanDate" date NOT NULL,
CONSTRAINT "PK" PRIMARY KEY ("ID")
)
WITH (
OIDS=FALSE
);
C # (требуется Devart.Data.dll и Devart.Data.PostgreSql.dll)
PgSqlConnection conn = new PgSqlConnection("Host=localhost; Port=5432; Database=testdb; UserId=postgres; Password=###########");
conn.Open();
PgSqlTransaction tx = conn.BeginTransaction(IsolationLevel.ReadCommitted);
for (int ii = 0; ii < 300000; ii++)
{
PgSqlCommand cmd = conn.CreateCommand();
cmd.Transaction = tx;
cmd.CommandType = CommandType.Text;
cmd.CommandText = "INSERT INTO public.\"sometable\" (\"Name\", \"Model\", \"ScanDate\") VALUES(@name, @model, @scanDate) RETURNING \"ID\"";
PgSqlParameter parm = cmd.CreateParameter();
parm.ParameterName = "@name";
parm.Value = "SomeName";
cmd.Parameters.Add(parm);
parm = cmd.CreateParameter();
parm.ParameterName = "@model";
parm.Value = "SomeModel";
cmd.Parameters.Add(parm);
parm = cmd.CreateParameter();
parm.ParameterName = "@scanDate";
parm.PgSqlType = PgSqlType.Date;
parm.Value = new DateTime(2011, 6, 1, 14, 12, 13);
cmd.Parameters.Add(parm);
cmd.Prepare();
long newID = (long)cmd.ExecuteScalar();
}
tx.Commit();
Это воссоздает переполнение памяти. ОДНАКО: если переменная 'cmd' создана и .Prepare () d вне цикла FOR, память не увеличивается! Очевидно, что подготовка нескольких команд PgSqlCommands с IDENTICAL SQL, но с разными значениями параметров не приводит к единому плану запросов внутри PostgreSQL, как это происходит в SQL Server.
Проблема остается: если кто-то использует Active Record Фаулера для вставки нескольких новых объектов, подготовленный общий доступ к экземплярам PgSqlCommand не будет элегантным.
Есть ли способ / опция, облегчающая повторное использование плана запросов с несколькими запросами, имеющими одинаковую структуру и разные значения аргументов?
UPDATE
Я решил взглянуть на простейший возможный случай - когда пакет SQL запускается непосредственно в СУБД, без ADO.NET (предложено Джордани). Удивительно, но PostgreSQL не сравнивает входящие запросы SQL и не использует повторно внутренние скомпилированные планы - даже когда входящий запрос имеет одинаковые идентичные аргументы! Например, следующая партия:
PostgreSQL (через pgAdmin -> Выполнить запрос) - захват памяти
BEGIN TRANSACTION;
INSERT INTO public."sometable" ("Name", "Model", "ScanDate") VALUES('somename', 'somemodel', '2011-06-01 14:12:19');
INSERT INTO public."sometable" ("Name", "Model", "ScanDate") VALUES('somename', 'somemodel', '2011-06-01 14:12:19');
-- the same INSERT is repeated 100.000 times
COMMIT;
SQL Server (через Management Studio -> Выполнить) - сохраняет использование памяти на одном уровне
BEGIN TRANSACTION;
INSERT INTO [dbo].sometable ("Name", "Model", "ScanDate") VALUES('somename', 'somemodel', '2011-06-01 14:12:19');
INSERT INTO [dbo].sometable ("Name", "Model", "ScanDate") VALUES('somename', 'somemodel', '2011-06-01 14:12:19');
-- the same INSERT is repeated 100.000 times
COMMIT;
и файл журнала PostgreSQL (спасибо, Sayap!) Содержит:
2011-06-05 16:06:29 EEST LOG: duration: 0.000 ms statement: set client_encoding to 'UNICODE'
2011-06-05 16:06:43 EEST LOG: duration: 15039.000 ms statement: BEGIN TRANSACTION;
INSERT INTO public."sometable" ("Name", "Model", "ScanDate") VALUES('somename', 'somemodel', '2011-06-01 14:12:19');
INSERT INTO public."sometable" ("Name", "Model", "ScanDate") VALUES('somename', 'somemodel', '2011-06-01 14:12:19');
-- 99998 lines of the same as above
COMMIT;
Очевидно, что даже после передачи всего запроса на сервер как есть сервер не может его оптимизировать.
Альтернативный драйвер ADO.NET
Как предположил Джордани, я попробовал драйвер NpgSql вместо dotConnect - с теми же (отсутствующими) результатами. Однако исходный код Npgsql для метода .Prepare () содержит такие поучительные строки:
planName = m_Connector.NextPlanName();
String portalName = m_Connector.NextPortalName();
parse = new NpgsqlParse(planName, GetParseCommandText(), new Int32[] { });
m_Connector.Parse(parse);
Новый контент в файле журнала:
2011-06-05 15:25:26 EEST LOG: duration: 0.000 ms statement: BEGIN; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
2011-06-05 15:25:26 EEST LOG: duration: 1.000 ms parse npgsqlplan1: INSERT INTO public."sometable" ("Name", "Model", "ScanDate") VALUES($1::varchar(255), $2::varchar(255), $3::date) RETURNING "ID"
2011-06-05 15:25:26 EEST LOG: duration: 0.000 ms bind npgsqlplan1: INSERT INTO public."sometable" ("Name", "Model", "ScanDate") VALUES($1::varchar(255), $2::varchar(255), $3::date) RETURNING "ID"
2011-06-05 15:25:26 EEST DETAIL: parameters: $1 = 'SomeName', $2 = 'SomeModel', $3 = '2011-06-01'
2011-06-05 15:25:26 EEST LOG: duration: 1.000 ms execute npgsqlplan1: INSERT INTO public."sometable" ("Name", "Model", "ScanDate") VALUES($1::varchar(255), $2::varchar(255), $3::date) RETURNING "ID"
2011-06-05 15:25:26 EEST DETAIL: parameters: $1 = 'SomeName', $2 = 'SomeModel', $3 = '2011-06-01'
2011-06-05 15:25:26 EEST LOG: duration: 0.000 ms parse npgsqlplan2: INSERT INTO public."sometable" ("Name", "Model", "ScanDate") VALUES($1::varchar(255), $2::varchar(255), $3::date) RETURNING "ID"
2011-06-05 15:25:26 EEST LOG: duration: 0.000 ms bind npgsqlplan2: INSERT INTO public."sometable" ("Name", "Model", "ScanDate") VALUES($1::varchar(255), $2::varchar(255), $3::date) RETURNING "ID"
2011-06-05 15:25:26 EEST DETAIL: parameters: $1 = 'SomeName', $2 = 'SomeModel', $3 = '2011-06-01'
2011-06-05 15:25:26 EEST LOG: duration: 0.000 ms execute npgsqlplan2: INSERT INTO public."sometable" ("Name", "Model", "ScanDate") VALUES($1::varchar(255), $2::varchar(255), $3::date) RETURNING "ID"
2011-06-05 15:25:26 EEST DETAIL: parameters: $1 = 'SomeName', $2 = 'SomeModel', $3 = '2011-06-01'
2011-06-05 15:25:26 EEST LOG: duration: 0.000 ms parse npgsqlplan3: INSERT INTO public."sometable" ("Name", "Model", "ScanDate") VALUES($1::varchar(255), $2::varchar(255), $3::date) RETURNING "ID"
Неэффективность вполне очевидна в этом отрывке из журнала ...
Выводы (такие как они)
Замечание Фрэнка о WAL - еще одно пробуждение: что-то еще, что настраивает этот SQL Server, скрывается от типичного разработчика MS.
NHibernate (даже в самом простом использовании) правильно использует подготовленные SqlCommands ... если только он использовался с самого начала ...
очевидно, что существует архитектурное различие между SQL Server и PostgreSQL и кодом , специально созданным для SQL Server (и, следовательно, блаженно не подозревающим о возможности невозможности повторного использования идентичного sql). ) не будет эффективно работать на PostgreSQL без серьезного рефакторинга. И рефакторинг 130+ устаревших классов ActiveRecord для повторного использования подготовленных объектов SqlCommand в грязном многопоточном промежуточном программном обеспечении не является делом типа «просто заменишь dbo-with-public».
К сожалению, из-за моего сверхурочного, Эивар ответит правильно:)
Спасибо всем, кто принял участие!