Как повысить производительность, обрабатывая результаты базы данных параллельно? - PullRequest
1 голос
/ 06 июля 2010

У меня есть приложение .net, которое запускает от 20 до 30 запросов SQL и обрабатывает результаты по одному за раз. Я пытался повысить производительность, выполняя некоторую работу параллельно.

2 запроса занимают 75% времени, просто из-за объема возвращаемых данных. Мои первые эксперименты состояли в том, чтобы попытаться разделить эти запросы на 4 сегмента, используя ntile, и параллельно обрабатывать каждый из них. Во всяком случае, это занимает намного больше времени, я думаю, из-за дополнительной работы, связанной с использованием NTILE +, запрашивающим БД 4 раза вместо 1.

Может кто-нибудь предложить другие методы, чтобы попробовать или я просто трачу свое время здесь? Приведенный ниже код является частью служебного класса, который позволяет мне ставить в очередь функции, которые обрабатывают читателя. Поэтому, используя мой эксперимент NTILE, я ставлю в очередь 4 задачи, каждая из которых обрабатывает 1/4 данных (где ntile = 1, 2, 3, 4) и вызывает Execute, чтобы запустить их параллельно.

foreach (var keyValuePair in m_Tasks)
            {
                var sql = keyValuePair.Key;
                var task = keyValuePair.Value;

                var conn = new OracleConnection(ConnectionString);
                conn.BeginOpen(o=> {
                    conn.EndOpen(o);
                    var cmd = conn.CreateCommand();
                    cmd.CommandText = sql;

                    cmd.BeginExecuteReader(a =>
                    {
                        var reader = cmd.EndExecuteReader(a);
                        DateTime endIO = DateTime.Now;
                        Console.WriteLine(TaskName + " " + Thread.CurrentThread.ManagedThreadId + "  IO took: " + (endIO - startTime) + " ended at " + endIO);

                        DateTime taskStart = DateTime.Now;
                        task(reader);
                        DateTime endTAsk = DateTime.Now;
                        Console.WriteLine(TaskName + " " + Thread.CurrentThread.ManagedThreadId + " TAsk took: " + (endTAsk - taskStart) + " ended at " + endTAsk);
                        reader.Close();
                        conn.Close();

                        if (Interlocked.Decrement(ref numTasks) == 0)
                        {
                            finishedEvent.Set();
                        }

                    }, null);

                },
                null

                    );


            }

            finishedEvent.WaitOne();
            DateTime endExecute = DateTime.Now;
            Console.WriteLine(TaskName + " " + Thread.CurrentThread.ManagedThreadId + " EXECUTE took: " + (endExecute - startTime) + " ended at " + endExecute);

        }

Спасибо за любую помощь.

Ответы [ 4 ]

0 голосов
/ 14 июля 2010

В конце концов, это оказалось проблемой, связанной с IO. Я смог добиться улучшения перфорирования, выполняя ввод-вывод асинхронно. NTILE на ROWID делает то, что я хотел, но пока это не помогло, потому что проблема связана с IO.

0 голосов
/ 06 июля 2010

Я использую OracleCommand.Fetchsize для улучшения производительности в больших запросах.

cmd.FetchSize = &H100000  '1Mb
Dim Rdr = cmd.ExecuteReader

Некоторое время назад я использовал Async Readers для получения данных BLOB-объектов.Но чтобы использовать Async Reader, вам нужно поддерживать массив с каждым асинхронным Результатом цикла до конца последнего Reader.

   Public Shared Function FromBlob(ByVal Id As String, ByVal Rv As String, ByVal cn As OracleConnection) As Proyecto
     Dim n As Integer, Prj As Proyecto = Nothing
     Dim Bf(2)() As Byte, arrAr(2) As IAsyncResult 'Para proceso asíncrono

     Dim Cmd As New OracleCommand( _
         "Select rv,fecha,Datos From Proyectos Where Id=:Id and Rv in (:Rv,'Av','Est')", cn)
     Cmd.BindByName = True
     Cmd.Parameters.Add("Id", OracleDbType.Varchar2, Id, ParameterDirection.Input)
     Cmd.Parameters.Add("Rv", OracleDbType.Varchar2, Rv, ParameterDirection.Input)
     If Rv Is Nothing Then Prj = Proyecto.Actprj
     Try
        Using Rdr As OracleDataReader = Cmd.ExecuteReader
            Do Until Rdr.Read = False
                Dim rv1 As String = Rdr.GetString(0)
                Select Case rv1
                    Case "Av" : n = 1   'Avance TND
                    Case "Est" : n = 2  'Datos Seguimiento Estudio Seguridad
                    Case Else : n = 0
                End Select
                If Rdr.IsDBNull(2) = False Then
                   Dim Blob As OracleBlob = Rdr.GetOracleBlob(2)
                   Dim Buffer(CInt(Blob.Length)) As Byte
                   Bf(n) = Buffer
                   arrAr(n) = Blob.BeginRead(Buffer, 0, Buffer.Length, Nothing, Blob)
                End If
            Loop
            If Bf(0) Is Nothing AndAlso Prj Is Nothing Then _
               MessageBox.Show("Fallo al cargar proyecto") : Return Nothing
            For n = 0 To Bf.Length - 1
                Dim ar As IAsyncResult = arrAr(n)
                If ar IsNot Nothing AndAlso ar.AsyncWaitHandle.WaitOne() Then
                   Dim blob As OracleBlob = DirectCast(ar.AsyncState, OracleBlob)
                   blob.EndRead(ar)
                   blob.Dispose()
                   If ar.IsCompleted Then
                      Using rd As New BinReader(New MemoryStream(Bf(n)))
                          If n = 0 Then
                             Prj = New Proyecto(rd, False)
                          Else
                             Dim entry = Proyecto.Entry.FromLob(rd), Index = Prj.IndexOf(entry)
                             If Index < 0 Then Prj.Add(entry) Else Prj(Index) = entry
                          End If
                      End Using
                   End If
                End If
            Next
        End Using
        Catch ex As Exception
            MessageBox.Show(ex.Message)
     End Try
     Return Prj
  End Function
0 голосов
/ 06 июля 2010

Вы можете использовать Ref Cursor с Oracle для выполнения Sql с одной OracleCommand:

  Dim cmd As New OracleCommand("Begin " _
  & "Open :1 for Select T.CODTRA,SIM,JLA CAL,SUP,RESP,SERV,SubStr(Aparato,1,3) SIS,PERS,(nvl(DUR,0) * 60) as Dur,t.DESTRA,g.DesTra Destrae,OBS from " & TraRec & " T, Trarec_Gee g where T.codtra <> 'RV' and T.Codtra=G.Codtra(+);" _
  & "Open :2 for Select Red,descr from Redes;" _
  & "Open :3 for Select * from Tr_Redes;" _
  & "Open :4 for Select CODTRA,T_COND,COND,DEMORA * 60 as DEMORA from " & TrCondic _
  & ";end;", cn)

  For n = 0 To 3 : cmd.Parameters.Add(Nothing, OracleDbType.RefCursor, ParameterDirection.Output) : Next
  Dim da As New OracleDataAdapter(cmd)
  da.Fill(0, 0, ds.Tnd, ds.Redes, ds.TrRedes, ds.TrCondic)

Примечание. Da.Fill (0, 0, T1, T2 ...) - это специальная функция Oracle для извлечения многих таблиц за один оператор.

0 голосов
/ 06 июля 2010

Я думаю, вы правы, что затраты на выполнение NTILE перевешивают экономию параллелизма.

Вам нужно использовать что-то, что разделит наборы запросов на четко разделенные наборы.

Если ваши запросы возвращают менее 15% от общего количества данных (приблизительно), то разбейте таблицы наИндекс (или индексированное поле, или функциональный индекс), вероятно, является вашей лучшей отправной точкой.

Пример: Предполагая, что ваши данные имеют числовой псевдоключ в каждой строке, создайте функциональный индекс для MOD (Id, 4) - это даст вам основанную на индексе версию вашего подхода NTILE.(Я не думаю, что у вас может быть функциональный индекс для NTILE).

Этот специфический подход, вероятно, контрпродуктивен - вы будете получать данные из одних и тех же блоков в разных потоках, что потенциально увеличивает I /O (зависит от памяти).

Способ, которым параллельный запрос Oracle стремится это сделать - при условии, что вы хотите обработать более 15% данных в таблице - это просто разбить таблицу на N физических кусков (используя rowid) и затем запуститьN 'полное сканирование' на этих кусках.

Я не уверен, что вы сможете повторить этот подход с внешнего интерфейса.Разделение по идентификатору ключа увеличивает стоимость прохождения индекса для каждой строки.

Что вам, вероятно, нужно, это то, что разделяет таблицу чем-то другим, а не ключом, или, если вы разделяете по ключу, разделяйте егопо диапазонам, а не NTILE подход.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...