大型SQL事务:PostgreSQL上的内存不足,但在SQL Server上运行

Pro*_*mer 15 sql sql-server postgresql dotconnect

我决定将我的C#守护程序应用程序(使用dotConnect作为ADO.NET提供程序)从SQL Server 2008 R2移动到PostgreSQL 9.0.4 x64(在Windows Server 2008 R2上).因此,我稍微修改了所有查询以匹配PostgreSQL语法,并且...卡在SQL Server上相同查询(从未在低版Express版本上)从未发生的行为上.

假设数据库包含2个非常简单的表,彼此之间没有任何关系.它们看起来有点像:ID,Name,Model,ScanDate,Notes.我有一个转换过程,它通过TCP/IP读取数据,处理它,启动事务并使用vanilla INSERT将结果放入上述2个表中.这些表最初是空的; 没有BLOB列.在糟糕的一天有大约500,000个INSERT,所有INSERT都包含在一个事务中(并且不能分成多个事务,顺便说一句).没有SELECT,UPDATE或DELETE.INSERT的一个例子(ID是bigserial - 自动自动增量):

INSERT INTO logs."Incoming" ("Name", "Model", "ScanDate", "Notes")
VALUES('Ford', 'Focus', '2011-06-01 14:12:32', NULL)
Run Code Online (Sandbox Code Playgroud)

SQL Server平静地接受负载,同时保持约200 MB的合理工作集.但是,PostgreSQL在事务运行时每秒额外占用30 MB(!)并迅速耗尽系统RAM.

我已经完成了我的RTFM并试图摆弄postgresql.conf:将"work_mem"设置为最小64 kB(这略微减缓了RAM占用),将"shared_buffers"/"temp_buffers"减少到最小(没有差别),但是无济于事.将事务隔离级别降低到Read Uncommitted没有帮助.ID ID BIGSERIAL(PK)上没有索引.SqlCommand.Prepare()没什么区别.没有建立并发连接:守护程序专门使用数据库.

似乎PostgreSQL无法应对令人难以置信的简单INSERT-fest,而SQL Server可以做到这一点.也许这是PostgreSQL快照-vs-SQL Server锁定隔离差异?对我来说这是事实:vanilla SQL Server可以工作,而vangre和PostgreSQL都没有.

在基于INSERT的事务运行时,我该怎么做才能使PostgreSQL内存消耗保持不变(显然是SQL Server的情况)?

编辑:我创建了一个人工测试用例:

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
);
Run Code Online (Sandbox Code Playgroud)

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();
Run Code Online (Sandbox Code Playgroud)

这重新创建了内存占用.但是:如果在FOR循环之外创建'cmd'变量并且.Prepare()d ,则内存不会增加!显然,使用IDENTICAL SQL但不同的参数值准备多个PgSqlCommands 不会导致PostgreSQL中的单个查询计划,就像在SQL Server中一样.

问题仍然存在:如果使用Fowler的Active Record dp插入多个新对象,则准备好的PgSqlCommand实例共享并不优雅.

是否有一种方法/选项可以促进查询计划重用,同时具有相同结构但不同参数值的多个查询?

UPDATE

我决定查看最简单的案例 - 在没有ADO.NET的情况下直接在DBMS上运行SQL批处理(由Jordani建议).令人惊讶的是,PostgreSQL不会比较传入的SQL查询,也不会重用内部编译的计划 - 即使传入的查询具有相同的相同参数!例如,以下批次:

PostgreSQL(通过pgAdmin - >执行查询) - hogs内存

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;
Run Code Online (Sandbox Code Playgroud)

SQL Server(通过Management Studio - > Execute) - 保持内存使用率不变

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;
Run Code Online (Sandbox Code Playgroud)

和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;
Run Code Online (Sandbox Code Playgroud)

显然,即使将整个查询按原样传输到服务器,服务器也无法对其进行优化.

ADO.NET驱动程序替代方案

正如Jordani建议的那样,我尝试使用NpgSql驱动程序而不是dotConnect - 结果相同(缺乏).但是,.Prepare()方法的Npgsql源包含这样的启发性行:

planName = m_Connector.NextPlanName();
String portalName = m_Connector.NextPortalName();
parse = new NpgsqlParse(planName, GetParseCommandText(), new Int32[] { });
m_Connector.Parse(parse);
Run Code Online (Sandbox Code Playgroud)

日志文件中的新内容:

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"
Run Code Online (Sandbox Code Playgroud)

在这个日志摘录中,低效率非常明显......

结论(如他们)

弗兰克关于WAL的说明是另一个觉醒:配置SQL Server隐藏在典型的MS开发人员之外的其他东西.

NHibernate(即使在最简单的用法中)正确地重用了准备好的SqlCommands ......如果只是从一开始就使用它...

很显然,SQL Server和PostgreSQL之间存在架构差异,而专门为 SQL Server 构建的代码(因此幸福地没有意识到'无法重用 - 相同 - sql'的可能性)将无法在没有主要的PostgreSQL上高效工作重构.并且重构130多个传统的ActiveRecord类,以便在混乱的多线程中间件中重用已准备好的SqlCommand对象,这不仅仅是"公正 - 替换 - 与公共"类型的事件.

不幸的是,对于我的加班,Eevar的回答是正确的:)

感谢所有投入的人!

Fra*_*ens 8

减少work_mem和shared_buffers不是一个好主意,数据库(包括PostgreSQL)喜欢RAM.

但这可能不是你最大的问题,那么WAL设置呢?wal_buffers应足够大以容纳整个事务,所有500k INSERT.目前的设置是什么?那checkpoint_segments呢?

500k INSERT应该不是问题,PostgreSQL可以在没有内存问题的情况下处理这个问题.

http://www.postgresql.org/docs/current/interactive/runtime-config-wal.html


eev*_*var 6

我怀疑你自己搞清楚了.您可能正在创建500k个不同的预准备语句,查询计划等等.实际上,它比那更糟糕; 准备好的语句存在于事务边界之外并持续直到连接关闭.像这样滥用它们会耗费大量内存.

如果要多次执行查询但避免每次执行的计划开销,请创建一个预准备语句并使用新参数重用该语句.

如果您的查询是唯一且临时的,只需使用postgres对绑定变量的常规支持; 不需要预备语句的额外开销.

  • @Proglamer:如果要进行批量插入,请考虑使用PgSql COPY命令或PgSqlLoader构造. (2认同)