我们假设数据库包含两个非常简单的表,彼此之间没有任何关系.他们看起来像这样: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;
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的答案是正确的:)
感谢所有投入的人!