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

我决定将我的C#守护程序应用程序(使用dotConnect作为ADO.NET提供程序)从sql Server 2008 R2移到Postgresql 9.0.4 x64(在 Windows Server 2008 R2上).因此,我略微修改了所有查询,以匹配Postgresql语法和…被困在sql Server上甚至没有发生同样的查询的行为(甚至不在低速版本上).

我们假设数据库包含两个非常简单的表,彼此之间没有任何关系.他们看起来像这样:ID,Name,Model,ScanDate,Notes.我有一个转换过程,通过TCP / IP读取数据,处理它,启动一个事务,并使用vanilla INSERT将结果放入上述的2个表中.表最初是空的没有BLOB列.在糟糕的一天有大约500.000个INSERT,全部包装在单个事务中(并且不能被拆分成多个事务btw).没有选择,更新或删除. INSERT的一个例子(ID是bigserial – 自动增量):

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

sql Server平静接受负载,同时保持约200 MB的合理工作集.然而,Postgresql每秒需要额外的30 MB(!),并迅速耗尽系统RAM.

我已经完成了我的RTFM,并尝试使用postgresql.conf:将“work_mem”设置为最小64 kB(这稍微减慢了RAM占用),将“shared_buffers”/“temp_buffers”减少到最小(无差异),但是无济于事将事务隔离级别降低为Read Uncommitted读取没有帮助.除了ID BIGSERIAL(PK)之外没有索引. sqlCommand.Prepare()没有区别.没有建立并发连接:守护程序专门使用数据库.

似乎Postgresql无法应对令人难以置信的简单的INSERT-fest,而sql Server可以做到这一点.也许这是一个Postgresql快照vs-sql Server锁隔离差异?这对我来说是一个事实:香草sql Server的工作原理,既不是香草也不是调整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
);

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循环之外,内存不会增加!显然,使用IDENTICAL sql准备多个PgsqlCommands但不同的参数值不会导致Postgresql中的单个查询计划,就像在sql Server中一样.

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

有没有方法/选项来促进查询计划重用,具有相同结构但不同参数值的多个查询

UPDATE

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

Postgresql(通过pgAdmin – >执行查询) – 内存

BEGIN TRANSACTION;

INSERT INTO public."soMetable" ("Name","ScanDate") VALUES('somename','somemodel','2011-06-01 14:12:19');
INSERT INTO public."soMetable" ("Name",'2011-06-01 14:12:19');
-- the same INSERT is repeated 100.000 times

COMMIT;

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

BEGIN TRANSACTION;

INSERT INTO [dbo].soMetable ("Name",'2011-06-01 14:12:19');
INSERT INTO [dbo].soMetable ("Name",'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",'2011-06-01 14:12:19');
-- 99998 lines of the same as above
COMMIT;

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

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

日志文件中的新内容

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","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",$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",$3 = '2011-06-01'
2011-06-05 15:25:26 EEST LOG:  duration: 0.000 ms  parse npgsqlplan2: INSERT INTO public."soMetable" ("Name",$3::date) RETURNING "ID"
2011-06-05 15:25:26 EEST LOG:  duration: 0.000 ms  bind npgsqlplan2: INSERT INTO public."soMetable" ("Name",$3 = '2011-06-01'
2011-06-05 15:25:26 EEST LOG:  duration: 0.000 ms  execute npgsqlplan2: INSERT INTO public."soMetable" ("Name",$3 = '2011-06-01'
2011-06-05 15:25:26 EEST LOG:  duration: 0.000 ms  parse npgsqlplan3: INSERT INTO public."soMetable" ("Name",$3::date) RETURNING "ID"

在这个日志摘录中效率低下很明显…

结论(如它们)

弗兰克关于WAL的注释是另一个觉醒:还有一件事情要配置sql Server隐藏在典型的MS开发人员身上.

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

很明显,sql Server和Postgresql之间存在架构上的差异,而专门为sql Server构建的代码(从而令人愉快地不了解“不能重复使用相同的sql”可能性)在Postgresql上无效,而没有主重构.并重构130个传统的ActiveRecord类,以便在一个凌乱的多线程中间件中重新使用准备好的sqlCommand对象不是一个’只是替换 – 与公开’类型的事情.

不幸的是我的加班,Eevar的答案是正确的:)

感谢所有投入的人!

解决方法

我怀疑你自己弄清楚了.你可能创建了500k种不同的准备语句,查询计划和所有.
其实比这更糟糕准备好的语句生效在事务边界之外,并持续到连接关闭.像这样滥用他们会消耗大量的记忆力.

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

如果您的查询是独一无二的,特殊的,只需使用postgres对绑定变量的常规支持;不需要预备语句的额外开销.

相关文章

SELECT a.*,b.dp_name,c.pa_name,fm_name=(CASE WHEN a.fm_n...
if not exists(select name from syscolumns where name=&am...
select a.*,pano=a.pa_no,b.pa_name,f.dp_name,e.fw_state_n...
要在 SQL Server 2019 中设置定时自动重启,可以使用 Window...
您收到的错误消息表明数据库 &#39;EastRiver&#39; 的...
首先我需要查询出需要使用SQL Server Profiler跟踪的数据库标...