#c# #tsql #sql-server-2008 #oledb #task-parallel-library
#c# #tsql #sql-server-2008 #oledb #задача-параллельная-библиотека
Вопрос:
Обнаружено, что OleDbConnection, похоже, не является потокобезопасным. Похоже, что вместо этого он пытается открыть несколько подключений.
//doesn't work
using (OleDbConnection oConn = TheDataAccessLayer.GetConnection())
using (OleDbTransaction oTran = oConn.BeginTransaction())
Parallel.ForEach(ORMObjects, (ORMObject, State) =>
{
if (!State.ShouldExitCurrentIteration amp;amp; !State.IsExceptional)
{
var Error = ORMObject.SomethingThatExecutesANonQuery(oConn,oTran)
if (Error.Number != 0)
State.Stop();
}
});
Если я заблокирую соединение для ExecuteNonQuery, ошибки исчезнут, но производительность снизится.
//works
using (OleDbConnection oConn = TheDataAccessLayer.GetConnection())
using (OleDbTransaction oTran = oConn.BeginTransaction())
Parallel.ForEach(ORMObjects, (ORMObject, State) =>
{
if (!State.ShouldExitCurrentIteration amp;amp; !State.IsExceptional)
{
lock(oConn)
{
var Error = ORMObject.SomethingThatExecutesANonQuery(oConn,oTran)
if (Error.Number != 0)
State.Stop();
}
}
});
Предположим, что
-
Я не могу изменить характер ORM: SQL не может быть увеличен
-
Бизнес-правила требуют, чтобы взаимодействие выполнялось в рамках одной транзакции
Итак:
-
Есть ли более лучший / более эффективный способ распараллеливания взаимодействий OleDb?
-
Если нет, есть ли альтернатива OleDb-клиенту, которая может в полной мере использовать преимущества параллелизма? (Может быть, собственный клиент MSSQL?)
Ответ №1:
Транзакции должны быть ACID, но «долговечность» должна применяться только в конце транзакции. Таким образом, физический ввод-вывод на диск может быть отложен после очевидного выполнения инструкции SQL и фактически выполняться в фоновом режиме, пока ваша транзакция обрабатывает другие инструкции.
Как следствие, последовательная выдача инструкций SQL может быть ненамного медленнее, чем их одновременная выдача. Рассмотрим этот сценарий:
- Выполните инструкцию SQL [A], которая записывает данные. Диск фактически не затрагивается, записи просто ставятся в очередь на потом, поэтому поток выполнения очень быстро возвращается клиенту (т. Е. [A] не блокируется надолго).
- Выполните инструкцию SQL [B], которая записывает данные. Записи ставятся в очередь, и [B] не блокируется надолго, как и раньше. На данный момент физический ввод-вывод [A] может уже выполняться в фоновом режиме.
- В транзакции выполняется другая обработка, в то время как СУБД выполняет физический ввод-вывод на диск в фоновом режиме.
- Транзакция зафиксирована.
- Если записи в очереди завершены, ждать не нужно.
- Если к настоящему времени записи в очереди не завершены, подождите, пока они не завершатся. Кстати, некоторые базы данных могут ослабить требования к «долговечности», чтобы избежать этого ожидания, но не MS SQL Server (AFAIK).
Конечно, существуют сценарии, в которых этот «автопараллелизм» СУБД не будет работать хорошо, например, когда есть WHERE
предложение, которое для разных операторов касается разных разделов на разных дисках — СУБД хотела бы распараллелить эти предложения, но не может, если они передаются в нее один за другим.
В любом случае, не угадывайте, где находится ваше узкое место в производительности. Измерьте это вместо этого!
Кстати, MARS не поможет вам в распараллеливании ваших операторов — согласно MSDN: «Однако обратите внимание, что MARS определяется в терминах чередования, а не в терминах параллельного выполнения».
Комментарии:
1. Здесь много полезной информации. Боюсь, что с вашей точки зрения, в итоге я получаю яблоки с апельсинами: я могу сравнить 1 соединение и 1 транзакцию, выполняемую последовательно, с 80 000 подключений и транзакций параллельно (без объединения в пул). Между ними разница в 15 секунд (всего около полутора минут) В зависимости от того, насколько эффективно работает объединение, я надеялся на существенную экономию, если бы мог поддерживать одно и то же соединение.
2. Дополнение: приведенный выше сценарий с 1 подключением блокировкой выполняется примерно на 10 секунд быстрее, чем последовательный, и примерно на 5 секунд медленнее, чем «полная параллель»
Ответ №2:
Обнаружено, что OleDbConnection, похоже, не является потокобезопасным.
Да, это соответствует документации:
Любые общедоступные статические (общие в Visual Basic) элементы этого типа являются потокобезопасными. Не гарантируется потокобезопасность любых членов экземпляра.
Поэтому просто создайте соединение внутри потока и предоставьте базовому поставщику OLE DB обрабатывать объединение соединений. Также, если у вас есть возможность, обязательно избавьтесь от OleDbConnection и используйте соответствующий ADO.NET драйвер для вашей базы данных, и если вы не используете какую-то очень экзотическую базу данных, должен быть ADO.NET водитель.
Комментарии:
1. Мне нужно поделиться транзакцией — бизнес-правила диктуют, что все откатывается при сбое.
Ответ №3:
Поскольку это не потокобезопасно, измените значение Parallel.ForEach
на нормальное foreach
и выполняйте их последовательно. Лучше, чтобы он работал медленнее, чем вообще не работал.
Ответ №4:
Чтобы получить максимальный прирост производительности, откройте новое соединение внутри вашего параллельного.ForEach. Таким образом, у вас будут настоящие параллельные подключения к базе данных.
Убедитесь, что у вас включен пул подключений и соответствующим образом заданы минимальные и максимальные свойства подключения.
Попробуйте этот подход и используйте класс секундомера, чтобы рассчитать производительность между различными подходами и выбрать тот, который лучше всего работает в вашем случае. Это зависит от типа запросов, которые вы будете выполнять к базе данных и схеме.