powered by simpleCommunicator - 2.0.49     © 2025 Programmizd 02
Форумы / WinForms, .Net Framework [игнор отключен] [закрыт для гостей] / Oracle.DataAccess.Client ест память класс Pooler
14 сообщений из 14, страница 1 из 1
Oracle.DataAccess.Client ест память класс Pooler
    #39912454
Фотография VSVLAD
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Доброго всем!

Есть приложение. Один из шагов, это раздробить огромный файл CSV и залить в оракловую таблицу. Делать нужно максимально быстро, поэтому читаю файл в несколько потоков (каждый поток читает свою часть строк с N до M).
Формирую через StringBuilder по 1000 строк такие простыни:
Код: plsql
1.
2.
3.
4.
5.
6.
begin
   insert into t1(a, b, c, d) values(1, 'data data', 'text', to_date('2020-01-10 12:00:00','yyyy-mm-dd hh24:mi:ss'));
   insert into t1(a, b, c, d) values(2, 'data data', 'text', to_date('2020-01-10 12:00:00','yyyy-mm-dd hh24:mi:ss'));
   insert into t1(a, b, c, d) values(3, 'data data', 'text', null);
   insert into t1(a, b, c, d) values(4, 'data data', null, null);
end;


и далее скармливаю методу-обёртке с доп. логикой. Для простоты перепишем код в таком виде:
Код: vbnet
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
    Public Shared Sub Exec(ByVal SQL As String)
        Using conn As New Oracle.DataAccess.Client.OracleConnection(oraString)
            conn.Open()
            
            Using cmd As Data.IDbCommand = conn.CreateCommand()
                cmd.CommandText = SQL
                cmd.ExecuteNonQuery()
            End Using
        End Using
    End Sub



Всё красиво работает и замечательно... но плохо то, что приложение сжирает 500 мб памяти сейчас и она не освобождается. Сборка GC не помогает, значит ссылка где-то есть. При том, как видим в коде всё вычищается, объекты диспозятся.

Далее, смотрим в профайлере куда это девается. Оказывается есть внутренний класс типа Oracle.DataAccess.Client.Pooler, который инициализируется для переменной Oracle.DataAccess.Client.UTF8CommandText.m_pooler так:
Код: c#
1.
internal static Pooler m_pooler = new Pooler(10, 200);



Он хранит до 200 последних текстов команд, они сортируются по частоте использования и ещё какая-то логика (подсмотрел через IL Spy). Похоже из-за static она внутри методов не чистится и не предполагалось, что в неё начнут пихать столько много запросов.

Может быть кто имел дело с этой библиотекой и возникала похожая проблема? Либо есть методы для чистки пула комманд (не путать с пуллом коннектов)

Переписывать параметрические запросы в данном случае не подойдёт. Т.к. это очень медленно. Либо у меня не завелось. Либо как-то сказать драйверу ODAC чтобы он открыл транзакцию и когда выполняю .ExecuteNonQuery() фактически нужно добавить в текущую транзакцию. Обрамлять в begin insert into t .... end; ещё не пробовал. Но не выполняя запрос в анонимном блоке, а прямо .CommandText = "insert into t ...." с подготовленными параметрами, всё равно работает медленно.
...
Рейтинг: 0 / 0
Oracle.DataAccess.Client ест память класс Pooler
    #39912518
carrotik
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
VSVLAD,

.. так а это .. sql*loader не пробовали? .. вроде он под это заточен ..

Oracle Bulk Import
...
Рейтинг: 0 / 0
Oracle.DataAccess.Client ест память класс Pooler
    #39912519
Фотография VSVLAD
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
carrotik,

не хотелось бы внешние утилиты тащить
...
Рейтинг: 0 / 0
Oracle.DataAccess.Client ест память класс Pooler
    #39912555
Фотография hVostt
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
VSVLAD
carrotik,

не хотелось бы внешние утилиты тащить


https://www.c-sharpcorner.com/article/two-ways-to-insert-bulk-data-into-oracle-database-using-c-sharp/
...
Рейтинг: 0 / 0
Oracle.DataAccess.Client ест память класс Pooler
    #39912556
Фотография hVostt
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
VSVLAD,

у вас совершенно отвратительный способ для вставки огромного массива данных
...
Рейтинг: 0 / 0
Oracle.DataAccess.Client ест память класс Pooler
    #39912573
Фотография buser
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
+1 - hVostt
Но феерия начинается чуть раньше "поэтому читаю файл в несколько потоков (каждый поток читает свою часть строк с N до M)"
...
Рейтинг: 0 / 0
Oracle.DataAccess.Client ест память класс Pooler
    #39912583
Фотография VSVLAD
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
buser,

если критикуешь, предлагай решение. Рабочее решение, с определёнными условиями
...
Рейтинг: 0 / 0
Oracle.DataAccess.Client ест память класс Pooler
    #39912593
Roman Mejtes
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
VSVLAD
buser,

если критикуешь, предлагай решение. Рабочее решение, с определёнными условиями

операция чтения из файла тут наименее затратная, можно просто почитать N строк, разбить и выполняться параллельно, а так еще не известно, + если длинная строки не фиксированная, нужно будет найти заданную строку, чтоб начать чтение с неё.
Ну и как по мне, всё это имеет смысл только тогда, когда вставка идет параллельно в разные таблицы, тогда скорость возрастёт сильно.
...
Рейтинг: 0 / 0
Oracle.DataAccess.Client ест память класс Pooler
    #39912618
Фотография VSVLAD
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Roman Mejtes,

Практически так и работает. Сначала считаем количество строк в файле. Эта операция на ~ 2.5 млн строк занимает где-то 1 секунду. Далее логически файл делим на сегменты и читает каждый таск (поток) свою часть строк, создаёт из них наборы инсертов и выполняет. Замерял, в многопоточном режиме, этот способ эффективнее, чем допустим читать файл в Parallel.ForEach или в одном потоке работать. Проблемы в скорости сейчас нет, заливка занимает около 15 минут в среднем. sqloader также чуточку быстрее работает, но не прям махом.

А проблема в том, что память odac съел. Приложение запускается шедулером и работает примерно 3-4 часа, включая выполнение других шагов. На самом деле приложение это не совсем работает как типичное приложение. Это самописный продвинутый "блокнот" который читает файл скрипта на VB.NET, компилирует в сборку в памяти и исполняет "пользовательский" код. Сделано затем, чтобы обычные пользователи не программисты могли запускать свои скрипты, которые имеют продвинутый доступ к базам, сети, ftp через классы-обертки выполняющие задачу в 1 строку. Конкретно в этой задаче потребовалось грузить csv в базу, такова задача, помимо чтение его с sftp и далее после обработки, через soap выполняются определенные методы. Возможно т.к. библиотека в домене приложения и статик переменная не подчищена, то ссылка на неё всё ещё жива, пока приложение запущено.
...
Рейтинг: 0 / 0
Oracle.DataAccess.Client ест память класс Pooler
    #39912733
Сон Веры Павловны
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
VSVLAD,

VSVLAD
Доброго всем!

Есть приложение. Один из шагов, это раздробить огромный файл CSV и залить в оракловую таблицу. Делать нужно максимально быстро, поэтому читаю файл в несколько потоков (каждый поток читает свою часть строк с N до M).
Формирую через StringBuilder по 1000 строк такие простыни:
Код: plsql
1.
2.
3.
4.
5.
6.
begin
   insert into t1(a, b, c, d) values(1, 'data data', 'text', to_date('2020-01-10 12:00:00','yyyy-mm-dd hh24:mi:ss'));
   insert into t1(a, b, c, d) values(2, 'data data', 'text', to_date('2020-01-10 12:00:00','yyyy-mm-dd hh24:mi:ss'));
   insert into t1(a, b, c, d) values(3, 'data data', 'text', null);
   insert into t1(a, b, c, d) values(4, 'data data', null, null);
end;


Раз уж решили сами свой загрузчик писать, то будьте в курсе поговорки "row by row is slow by slow". В данном случае надо заполнять блоками фиксированного размера пакетированный экземпляр коллекции, а по достижении этого размера уже делать bulk-вставку в целевую таблицу через forall..insert. Фиксированного размера - чтобы не забить весь доступный UGA (или, еще хуже, выбрать свой лимит SGA, если пакет с прагмой SERIALLY_REUSABLE).
Как-то так:
Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
create table TEST (
  id number not null,
  td date not null,
  name varchar2(1000) not null
);
/
create or replace package pk_bulk_insert
as
  type
    t_bulk_rec is record (
      id number,
      td date,
      name varchar2(1000)
    );
  type
    t_bulk_data is table of t_bulk_rec;
  procedure Init;
  procedure Add(id number, td date, name varchar2);
  procedure Commit;
end pk_bulk_insert;
/
create or replace package body pk_bulk_insert
as
  MAX_BUFFER_SIZE constant binary_integer:=100;
  empty_data t_bulk_data:=t_bulk_data();
  bulk_data t_bulk_data;

  procedure Init
  is
  begin
    bulk_data:=empty_data;
  end;

  procedure InsertData
  is
  begin
    forall idx in bulk_data.first..bulk_data.last
      insert into TEST (id, td, name)
      values (
        bulk_data(idx).id,
        bulk_data(idx).td,
        bulk_data(idx).name
      );
    bulk_data:=empty_data;
  end;

  procedure Add(id number, td date, name varchar2)
  is
  begin
    if bulk_data.count>=MAX_BUFFER_SIZE then
      InsertData;
    end if;
    bulk_data.extend;
    bulk_data(bulk_data.count).id:=id;
    bulk_data(bulk_data.count).td:=td;
    bulk_data(bulk_data.count).name:=name;
  end;

  procedure Commit
  is
  begin
    if bulk_data.count>0 then
      InsertData;
    end if;
    commit;
  end;
end pk_bulk_insert;
/

Небольшой бенчмарк:
Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
public class Program
{
  public static void Main(string[] args)
    => BenchmarkRunner.Run<Program>();

  string _bulkSql;
  string _directSql;
  [GlobalSetup]
  public void CreateScipts()
  {
    var bulkBuilder = new StringBuilder();
    var directBuilder = new StringBuilder();
    bulkBuilder.AppendLine("begin").AppendLine("execute immediate 'truncate table TEST';").AppendLine("pk_bulk_insert.Init;");
    directBuilder.AppendLine("begin").AppendLine("execute immediate 'truncate table TEST';");
    for(var i=0;i<1000;i++)
    {
      bulkBuilder.AppendLine($"pk_bulk_insert.Add({i}, sysdate+{i}, '{(char)(i%26+65)}');");
      directBuilder.AppendLine($"insert into TEST (id, td, name) values ({i}, sysdate+{i}, '{(char) (i % 26 + 65)}');");
    }
    bulkBuilder.AppendLine("pk_bulk_insert.Commit;").AppendLine("end;");
    directBuilder.AppendLine("commit;").AppendLine("end;");
    _bulkSql = bulkBuilder.ToString();
    _directSql = directBuilder.ToString();
  }

  [Benchmark]
  public void DirectInsert()
  {
    using(var cnn = new OracleConnection(<.....>))
    using(var cmd = new OracleCommand(_directSql, cnn))
    {
      cnn.Open();
      cmd.ExecuteNonQuery();
    }
  }

  [Benchmark]
  public void BulkInsert()
  {
    using(var cnn = new OracleConnection(<.....>))
    using(var cmd = new OracleCommand(_bulkSql, cnn))
    {
      cnn.Open();
      cmd.ExecuteNonQuery();
    }
  }
}


Результат:

Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
// * Summary *

BenchmarkDotNet=v0.12.0, OS=Windows 7 SP1 (6.1.7601.0)
Intel Core i5-3470 CPU 3.20GHz (Ivy Bridge), 1 CPU, 4 logical and 4 physical cores
Frequency=3117998 Hz, Resolution=320.7186 ns, Timer=TSC
  [Host]     : .NET Framework 4.8 (4.8.3928.0), X64 RyuJIT
  DefaultJob : .NET Framework 4.8 (4.8.3928.0), X64 RyuJIT


|       Method |      Mean |     Error |     StdDev |
|------------- |----------:|----------:|-----------:|
| DirectInsert | 947.81 ms | 57.476 ms | 168.568 ms |
|   BulkInsert |  52.54 ms |  2.425 ms |   7.151 ms |

Причина такой картины очень проста: 10 физических обращений к накопителю в случае BulkInsert, и 1000 физических обращений в случае DirectInsert.
...
Рейтинг: 0 / 0
Oracle.DataAccess.Client ест память класс Pooler
    #39912739
Фотография hVostt
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
VSVLAD
А проблема в том, что память odac съел.


Проблема не в этом. Вы сами генерируете конские тексты запросов. Какого, спрашивается?
...
Рейтинг: 0 / 0
Oracle.DataAccess.Client ест память класс Pooler
    #39912897
Фотография VSVLAD
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
hVostt,

спасибо за ссылку, буду на работе, попробую использовать Oracle.DataAccess.Client.OracleBulkCopy
...
Рейтинг: 0 / 0
Oracle.DataAccess.Client ест память класс Pooler
    #39913164
Фотография VSVLAD
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Сон Веры Павловны, спасибо за пример. Но я пошёл путём попроще, через класс OracleBulkCopy.

Класс и память корретно освобождает и заливает 500К (42 Мб файл) за 5-6 секунд. Самый большой файл у меня 150 Мб пока

Готовое решение на VB, кому интересно в спойлере.
Код: vbnet
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
Public Shared Sub UploadToOracle(ByVal Context As Object)
        Context.Info.Print("Чтение файла-дампа ...")
        Context.Info.Print("Всего строк: " & FileLineCount(Context, RftOptions.ftpLocalFile))
        
        Dim queryDateFormat = "yyyy-MM-dd HH:mm:ss", queryCount As Integer, N As Integer
        Dim dtDump As New DataTable
        
        ' Создаём структуру таблицы в DataTable с типами
        With dtDump.Columns
            .Add("b1")
            .Add("b2")
            .Add("b3")
            .Add("b4")
            .Add("b5")
            .Add("b6")
        End With
        
        With dtDump
            .Columns(0).DataType = GetType(String)
            .Columns(1).DataType = GetType(String)
            .Columns(2).DataType = GetType(Date)
            .Columns(3).DataType = GetType(Integer)
            .Columns(4).DataType = GetType(Date)
            .Columns(5).DataType = GetType(String)
        End With
        
        Try
            ' По каждой строке
            For Each line As String In Context.File.ReadLine(RftOptions.ftpLocalFile)
                N += 1
                
                ' Дробим CSV на запчасти
                Dim fields() As String = line.Split(";")                
                Dim dr As DataRow      = dtDump.NewRow()

                dr("b1")  = CStr(fields(0))
                dr("b2") = CStr(fields(1))
                dr("b3") = If(Not String.IsNullOrEmpty(fields(2)), Date.ParseExact(fields(2).Substring(0, 19), queryDateFormat, Globalization.CultureInfo.InvariantCulture), DBNull.Value)
                dr("b4") = CInt(fields(3))
                dr("b5") = If(Not String.IsNullOrEmpty(fields(4)), Date.ParseExact(fields(4).Substring(0, 19), queryDateFormat, Globalization.CultureInfo.InvariantCulture), DBNull.Value)
                dr("b6") = CStr(fields(5).Trim())
                dtDump.Rows.Add(dr)
                
                ' Каждые 100К строк фиксируем в базе
                queryCount += 1
                If queryCount Mod 100000 = 0 Then
                    Context.Info.Print("Выполняем вставку #" & Thread.CurrentThread.ManagedThreadId & " всего: " & queryCount)
                    
                    ' Реализация паузы
                    Do While Context.Info.IsPaused()
                        Context.Info.Pause(10)
                    Loop
                    
                    ' Реализация отмены
                    If Context.Info.IsStopped() Then Exit For
                    
                    ' Выполняем запрос
                    BulkInsert(oraString, "blabla", dtDump)
                    dtDump.Clear()
                End If
            Next
            
            ' Вставка остатка
            If dtDump.Rows.Count > 0 Then BulkInsert(oraString, "blabla", dtDump)
        
        Catch ex As Exception
            Context.Info.NewError("Вставка выполнилась с ошибками! Текущая пачка запросов: " & queryCount & ". Ошибка: " & Context.Info.LastError)
        End Try
  
        Context.Info.Print("Файл дампа залит в таблицу")
    End Sub

...
Рейтинг: 0 / 0
Oracle.DataAccess.Client ест память класс Pooler
    #39913237
Фотография VSVLAD
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
+ Сама процедура. Забыл про неё

Код: vbnet
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
    Public Shared Sub BulkInsert(ByVal ConnectionString As String, ByVal TableName As String, DT As DataTable)
        Using conn As New OracleConnection(ConnectionString)
            conn.Open()

            Using bulkCopy As New OracleBulkCopy(conn, OracleBulkCopyOptions.UseInternalTransaction)
                bulkCopy.DestinationTableName = TableName
                bulkCopy.BulkCopyTimeout = 1200
                bulkCopy.WriteToServer(DT)
            End Using
        End Using
    End Sub
...
Рейтинг: 0 / 0
14 сообщений из 14, страница 1 из 1
Форумы / WinForms, .Net Framework [игнор отключен] [закрыт для гостей] / Oracle.DataAccess.Client ест память класс Pooler
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]