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实例共享并不优雅.
是否有一种方法/选项可以促进查询计划重用,同时具有相同结构但不同参数值的多个查询?
我决定查看最简单的案例 - 在没有ADO.NET的情况下直接在DBMS上运行SQL批处理(由Jordani建议).令人惊讶的是,PostgreSQL不会比较传入的SQL查询,也不会重用内部编译的计划 - 即使传入的查询具有相同的相同参数!例如,以下批次:
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)
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)
显然,即使将整个查询按原样传输到服务器,服务器也无法对其进行优化.
正如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的回答是正确的:)
感谢所有投入的人!
减少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
我怀疑你自己搞清楚了.您可能正在创建500k个不同的预准备语句,查询计划等等.实际上,它比那更糟糕; 准备好的语句存在于事务边界之外并持续直到连接关闭.像这样滥用它们会耗费大量内存.
如果要多次执行查询但避免每次执行的计划开销,请创建一个预准备语句并使用新参数重用该语句.
如果您的查询是唯一且临时的,只需使用postgres对绑定变量的常规支持; 不需要预备语句的额外开销.