Параллельное программирование с C # - PullRequest
0 голосов
/ 03 января 2012

Я хочу заполнить таблицу базы данных в базе данных SQL с помощью C #. Я должен найти отдельный пакет в базе данных, а затем для каждого пакета сначала заполнить таблицу данных, содержащую все строки для этого пакета, а затем найти глубину для этой строки. Я использую следующую функцию для ускорения процесса с помощью параллельного управления задачами:

private void DoParrallelComputing(DataTable dtpacket, string Identifier)
{
  con = new SqlConnection(strConnect);

  Dictionary<int, DataTable> dtsName = new Dictionary<int, DataTable>();
  Dictionary<int, SqlDataAdapter> ReaderName = new Dictionary<int, SqlDataAdapter>();
  Dictionary<int, string> strName = new Dictionary<int, string>();
  Dictionary<int, SqlCommand> cmdName = new Dictionary<int, SqlCommand>();

  Dictionary<int, int> Node = new Dictionary<int, int>();
  Dictionary<int, string> NodeID = new Dictionary<int, string>();
  Dictionary<int, string> ParentID = new Dictionary<int, string>();
  Dictionary<int, int> Depth = new Dictionary<int, int>();

  Parallel.For(0, dtpacket.Rows.Count - 1, i =>
  {
    progressBarTree.Value = i;

    Application.DoEvents();
    string packetID = dtpacket.Rows[i][0].ToString();

    strName[i] = "";
    strName[i] = "SELECT Node,NodeID,ParentId,Depth from tblTree where [Identifier]='" + Identifier + "' and [PacketId]='" + packetID + "'";
    strName[i] = strName[i] + " order by [Node]";

    if (con.State != ConnectionState.Open)
    {
      con.Open();
    }

    ReaderName[i] = new SqlDataAdapter(strName[i], con);
    dtsName[i] = new DataTable();

    ReaderName[i].Fill(dtsName[i]);

    int rowsCount = 0;
    rowsCount = dtsName[i].Rows.Count - 1;

    for (int j = rowsCount; j >= 0; j--)
    {
      Application.DoEvents();
      Node[i] = Form1.val_int(dtsName[i].Rows[j][0].ToString());
      NodeID[i] = dtsName[i].Rows[j][1].ToString();
      ParentID[i] = dtsName[i].Rows[j][2].ToString();
      Depth[i] = 0;

      for (int ii = j - 1; ii >= 0; ii--)
      {
        string dtParent = "";
        string nodeParent = "";

        dtParent = dtsName[i].Rows[ii][1].ToString();
        nodeParent = ParentID[i];

        if (dtParent == nodeParent)
        {
          ParentID[i] = dtsName[i].Rows[ii][2].ToString();
          Depth[i]++;
        }
      }

      //Update Tree table
      strName[i] = "update tblTree set depth=" + Depth[i] + " where node=" + Node[i].ToString();

      if (con.State != ConnectionState.Open)
      {
        con.Open();
      }

      cmdName[i] = new SqlCommand(strName[i], con);
      cmdName[i].ExecuteNonQuery();
      cmdName[i].Dispose();
    }
  });
}

Но когда я запускаю эту функцию, я вижу, что загрузка процессора увеличивается до 70% с 4-ядерным процессором, а также приложение использует около 300 потоков, но через 10 минут я не вижу никаких изменений в базе данных! Если я не использую Параллельные Задачи, то для завершения мне потребуется около 20 дней, поэтому я хочу использовать Параллельные Задачи.

1 Ответ

4 голосов
/ 03 января 2012
  1. Нельзя строить команды SQL путем объединения значений в нем, таких как [Identifier]='" + Identifier + "'. Это вектор атаки SQL-инъекций, вы никогда не должны писать такой код. Используйте параметры: [Identifier] = @identifier и добавьте значение параметра в команду. Прежде чем продолжить, сделайте себе одолжение и прочитайте Как код доступа к данным влияет на производительность базы данных .

  2. Добавление потоков и тошноты не заставляет код работать быстрее, он будет работать медленнее . Если вы хотите добиться большей пропускной способности, вы должны использовать асинхронные команды базы данных. Убедитесь, что вы украсили строку подключения с помощью AsyncronouwProcessing = true, затем используйте BeginExecuteReader. Отрегулируйте количество невыполненных запросов.

  3. Использование DataAdapater и DataTable - верный способ снизить производительность. Используйте raw SqlDataReader . Я снова отсылаю вас к статье Боба Бошамина, на которую ссылались ранее.

  4. Не выполняйте обработку на клиенте, SQL Server намного лучше .

  5. Не выполняйте построчно-медленную обработку строк, используйте ориентированную на множество логику. Рекурсивные CTE могут использоваться для иерархической обработки, см. Использование общих табличных выражений .

Есть еще много проблем с вашим кодом (например, наличие цикла событий, четкое указание на то, что этот код не принадлежит потоку пользовательского интерфейса, частые вызовы ToString(), ясно указывающие на отсутствие понимания системы типов явные вызовы Dispose вместо того, чтобы полагаться на блоки using(...), слепое повторное открытие соединений и т. д. и т. д. и т. п.

В первую очередь вы должны сосредоточиться на решении проблемы, выражая ее в виде единого оператора UPDATE на основе CTE. Без полного определения схемы данных и требований можно только догадываться, что вы пытаетесь сделать, но это будет примерно так:

with cte as (
  select Node, NodeID, ParentId, Depth, 0 as ComputedDepth
  from tblTree 
  where [Identifier]= @Identifier
  union all
  select c.Node, c.NodeID, c.ParentId, c.Depth, p.ComputedDepth+1
  from tblTree c
  join cte p on c.ParentId = p.NodeId)
update cte
  set Depth = ComputedDepth;

Вот фактический пример:

create table tblTree (
[NodeID] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY, 
[Identifier] [varchar](10) NULL, 
[ParentID] [int] NULL, 
[Depth] [tinyint] NULL);
GO

insert into tblTree (Identifier, ParentID) 
    values ('Foo', NULL)
        , (NULL, 1)
        , (NULL, 1)
        , (NULL, 2)
        , (NULL, 4)
        , ('Bar', NULL);
go

declare @identifier VARCHAR(10) = 'Foo';
with cte as (
    select NodeID, 0 as Depth
    from tblTree
    where Identifier = @identifier
    union all
    select c.NodeID, p.Depth+1
    from tblTree c
    join cte p on c.ParentID = p.NodeID)
update t
    set t.Depth = c.Depth
from tblTree t 
join cte c on t.NodeID = c.NodeID;
go

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