Top.Mail.Ru
Мастера DELPHI, Delphi programming community Титульная страница Поиск, карта сайта Написать письмо 
| Новости | Поиск | FAQ | Статьи | Книги | Новости VCL |
| Форумы ЧАТ | Орешник | KOL и MCK |
Форумы -» Основная («Начинающим»)/ Базы / WinAPI / Компоненты / Сети / Media / Игры / Corba и COM / KOL / FreePascal / .Net / Прочее / RSDN.ORG

« предыдущая ветвь | форум | следующая ветвь »

Обмен большими данными между Oracle и FireBird [D6, IB5.x]


ildarkh ©   (2007-01-18 15:51:00) 

Существует такая проблема. Есть БД Oracle с таблицей на 500 тыс. записей. Нужно их все перегнать в соответствующую таблицу FireBird. Используется DBExpress.
Я написал с этой целью программу, поначалу крепко зависавшую из-за объема данных(в том числе на сервере с 3ггц Xeon и 4гб памяти). В случае сервера потребление памяти программой выростало до 500 мегабайт. Однако на меньших объемах данных программа работала исправно.
Я изменил код, который отвечает за запрос к Oracle. Но я что-то сделал неправильно. Программа продолжает жрать память и затем зависать. Код ниже, благодарю заранее

procedure TSelectInserts.SelInsExec(selins: IXMLSelectInsertType);
var i: integer;
repwith: string;
errstr: string;
TD: TTransactionDesc;
begin
try
Application.ProcessMessages;
TD.TransactionID := 1;
TD.IsolationLevel := xilREADCOMMITTED;
db2.StartTransaction(TD);
errstr := selins.Select.Query;
self.SelInsWork := 1;

db1.SQLClientDataSet.PacketRecords := 10000;
db1.SQLClientDataSet.FetchOnDemand := false;

db1.SelectQuery(selins.Select.Query, selins.Select.Params);
self.SelInsWork := 2;
i:=0;
while not db1.SQLClientDataSet.Eof do
 begin
 //db1.SQLClientDataSet.First;
 if db1.SQLClientDataSet.RecordCount > 0 then
 //for i:=1 to db1.SQLClientDataSet.RecordCount do
 while not db1.SQLClientDataSet.Eof do
   begin
   inc(i);
   self.InsProgressNum := i;
   errstr := selins.Insert.Query;
   if (i >= 1000 )and (i mod 1000 = 0) then
     begin
     db2.Commit(TD);
     db2.StartTransaction(TD);
     end;
   db2.UpdateQuery(selins.Insert.Query, selins.Fields, db1.DataSource.DataSet.Fields);
   db1.SQLClientDataSet.Next;
   Application.ProcessMessages;
   end;
 if db1.SQLClientDataSet.RecordCount > 0 then
 for i:=0 to selins.Select.Params.Count-1 do
   begin
   repwith := selins.Select.Params.Param[i].Replacewith;
   if(repwith <> '') then
      selins.Select.Params.Param[i].Text := db1.DataSource.DataSet.Fields.FieldByName(repwith).AsString;
 end;
 db1.SQLClientDataSet.GetNextPacket;
 end;
db2.Commit(TD);
xmldoc.SaveToFile(ExtractFilePath(ParamStr(0)) + 'XML.xml');
except on e: Exception do
 begin
 db2.Rollback(TD);
 Logger.Write(3, e.Message + ': ' + errstr);
 end;
end;
self.SelInsWork := 0;
end;


ildarkh   (2007-01-19 12:15:00) [1]


> > Отладчиком пользоваться умею, иначе как бы я написал предыдущий
> пост.
> Не видно. Место ошибки не указал, условия ее возникновения
> слишком расплывчаты.


db1.SQLDataSet.Next;
Application.ProcessMessages;

В этом фрагменте происходит вылет, о котором я выше говорил


> Есть. Выкинуть все нафиг и написать заново.
>

Надо исправить то, что есть, извини


Sergey13   (2007-01-19 11:01:00) [2]

> [11] ildarkh   (19.01.07 09:27)

А DataPump не пробовал?


Desdechado   (2007-01-19 10:49:00) [3]

> Отладчиком пользоваться умею, иначе как бы я написал предыдущий пост.
Не видно. Место ошибки не указал, условия ее возникновения слишком расплывчаты.


ANB   (2007-01-19 10:41:00) [4]


> Может есть какие-то более конкретные рекомендации?

Есть. Выкинуть все нафиг и написать заново. Код перекачки из БД в БД - максимум 10 строк (если таблицы одинаковой структуры и данные переливаются один в один, при этом список полей заранее известен, т.е. запросы на чтение и вставку можно подготовить заранее).


ildarkh   (2007-01-19 09:27:00) [5]

Может есть какие-то более конкретные рекомендации? Отладчиком пользоваться умею, иначе как бы я написал предыдущий пост.


Desdechado   (2007-01-18 21:03:00) [6]

Такое засорение кода - ужас!
Отладчиком пользоваться умеешь?

ЗЫ зачем такое количество выворотов? там все просто, как рельсы, должно быть


ildarkh   (2007-01-18 19:35:00) [7]

я изменил исходник по вашим советам, теперь процедура выглядит так
procedure TSelectInserts.SelInsExec(selins: IXMLSelectInsertType);
var i: integer;
repwith: string;
errstr: string;
TD: TTransactionDesc;
begin
try
Application.ProcessMessages;
TD.TransactionID := 1;
TD.IsolationLevel := xilREADCOMMITTED;
db2.StartTransaction(TD);
errstr := selins.Select.Query;
self.SelInsWork := 1;

//db1.SQLClientDataSet.PacketRecords := 10000;
//db1.SQLClientDataSet.FetchOnDemand := false;

db1.SelectQuery(selins.Select.Query, selins.Select.Params);
self.SelInsWork := 2;
i:=0;
//while not db1.SQLDataSet.Eof do
 begin
 db1.SQLDataSet.First;
 //if db1.SQLDataSet.RecordCount > 0 then
 //for i:=1 to db1.SQLClientDataSet.RecordCount do
 while not db1.SQLDataSet.Eof do
   begin
   inc(i);
   self.InsProgressNum := i;
   errstr := selins.Insert.Query;
   if (i >= 1000 )and (i mod 1000 = 0) then
     begin
     db2.Commit(TD);
     db2.StartTransaction(TD);
     end;
   db2.UpdateQuery(selins.Insert.Query, selins.Fields, db1.DataSource.DataSet.Fields);
   db1.SQLDataSet.Next;
   Application.ProcessMessages;
   end;
 if db1.SQLDataSet.RecordCount > 0 then
 for i:=0 to selins.Select.Params.Count-1 do
   begin
   repwith := selins.Select.Params.Param[i].Replacewith;
   if(repwith <> '') then
      selins.Select.Params.Param[i].Text := db1.DataSource.DataSet.Fields.FieldByName(repwith).AsString;
 end;
 //db1.SQLClientDataSet.GetNextPacket;
end;
db2.Commit(TD);
xmldoc.SaveToFile(ExtractFilePath(ParamStr(0)) + 'XML.xml');
except on e: Exception do
 begin
 db2.Rollback(TD);
 Logger.Write(3, e.Message + ': ' + errstr);
 end;
end;
self.SelInsWork := 0;
end;

Исключения, как видите перехватываются и журналируются. В этой процедуре есть вызов другой процедуры, UpdateQuery, которая записывает данные в БД-получатель
procedure TMSQLConnection.UpdateQuery(query: string; XMLfields: IXMLFieldsType; SQLfields: TFields);
var i: integer;
values: string;
begin
{self.SQLClientDataSet.Close;
self.SQLClientDataSet.CommandType := ctQuery;
self.SQLClientDataSet.CommandText := query;
self.SQLClientDataSet.Execute;}
try
 query := query + '(';
 values := ' values (';
 for i:=0 to XMLFields.Count-1 do
   begin
   query := query + XMLFields.Field[i].Db2;
   if(XMLFields.Field[i].Db1 <> '') then
       values := values + #39 + SQLfields.FieldByName(XMLFields.Field[i].Db1).AsString + #39
   else if(XMLFields.Field[i].Value <> '') then
     values := values + #39 + XMLFields.Field[i].Value + #39
   else values := values + XMLFields.Field[i].Func;
   if(i < XMLFields.Count-1) then
     begin
     query := query + ' , '; values := values + ' , ';
     end;
   end;
 query := query + ')';
 values := values + ')';

 self.SQLDataSet.Close;
 self.SQLDataSet.CommandText := query + values;
 Logger.Write(2, query + values);
 self.SQLDataSet.ExecSQL(false);

except on e: Exception do
 begin
 Logger.Write(3, e.Message + ': ' + query + values);
 end;
end;
end;

Эта вызываемая процедура нормально срабатывает в случае небольшой выборки(или возможно по другой причине). Однако в случае большой выборки, которую я упоминал вначале в первой процедуре вываливаются циклически окошки с ошибкой "DBX Error: Operation Not Supported.", не смотря на try...exept. Данные в БД не заносятся, хотя во второй процедуре исключения нет.


Desdechado   (2007-01-18 17:15:00) [8]

Про ClientDataSet забудь. Он здесь не нужен в принципе. Достаточно однонаправленного SQLDataSet.

> Зачем вообще нужна передача пакетами в TSQLClientDataSet?
Во-первых, не передача, а прием в CDS.
Во-вторых, это сделано для того, чтобы в случае больших выборок на клиента не замораживать интерфейс вычиткой всей выборки сразу. Может, пользователю будет достаточно поглядеть на первых несколько пакетов, а еще 200 тыс записей ему и не нужны будут. А без пакетирования CDS их сначала все вытянет на клиента (если памяти хватит), а потом разморозит интерфейс.

> Я читал, что с помощью них можно поэтапно откачивать данные с сервера БД.
Правильно. Но только не длятвоего случая. Поскольку CDS запоминает (кэширует) данные на клиенте, а тебе нужно не это.


Johnmen   (2007-01-18 16:42:00) [9]


> Зачем вообще нужна передача пакетами в TSQLClientDataSet?


Ну, видимо, быстрее пакетами. Хотя, я думаю, это "быстрее" никто не видел :)


ildarkh   (2007-01-18 16:36:00) [10]

Зачем вообще нужна передача пакетами в TSQLClientDataSet? Я читал, что с помощью них можно поэтапно откачивать данные с сервера БД.


ildarkh   (2007-01-18 16:33:00) [11]

Спасибо, я попробую.


Johnmen   (2007-01-18 16:31:00) [12]

Ты непрв во всём.
Как сказал Desdechado, читаешь записи из входного набора данных, и тут же их пишешь (INSERT INTO). По одной.
Использовать лучше TSQLQuery.


ildarkh   (2007-01-18 16:19:00) [13]

Во внешнем цикле проход осуществляется
db1.SQLClientDataSet.GetNextPacket;
Во внутреннем
db1.SQLClientDataSet.Next;

А признак db1.SQLClientDataSet.Eof вроде быподходит для обоих случаев. Если я не прав, то скажите, в чем?


Desdechado   (2007-01-18 16:14:00) [14]

Зачем тебе CDS? Он кэширует данные на клиенте.
Тебе же, как я понял, надо построчно читать и тут же записывать.
Вот так и делай. Достаточно SQLDataSet использовать.


Johnmen   (2007-01-18 16:04:00) [15]

while not db1.SQLClientDataSet.Eof do
begin

//db1.SQLClientDataSet.First;
if db1.SQLClientDataSet.RecordCount > 0 then
//for i:=1 to db1.SQLClientDataSet.RecordCount do
while not db1.SQLClientDataSet.Eof do
  begin


Это так надо ? :)))


Наверх

Титульная страница Поиск, карта сайта Написать письмо