Я не мог получить более 700000 строк с SQL Server с помощью C# - я получаю исключение «нехватка памяти». Пожалуйста, помогите мне.
Это мой код:
using (SqlConnection sourceConnection = new SqlConnection(constr))
{
sourceConnection.Open();
SqlCommand commandSourceData = new SqlCommand("select * from XXXX ", sourceConnection);
reader = commandSourceData.ExecuteReader();
}
using (SqlBulkCopy bulkCopy = new SqlBulkCopy(constr2))
{
bulkCopy.DestinationTableName = "destinationTable";
try
{
// Write from the source to the destination.
bulkCopy.WriteToServer(reader);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
reader.Close();
}
}
Я создал небольшое консольное приложение на основе данного решения 1, но в конечном итоге с тем же исключением, также я опубликовал свой процесс памяти до и после
Перед обработкой: 
После добавления тайм-аута команды на стороне считываемого кода Ram достигает пика,

Пакетная обработка, чтобы вы загружали только x записей за раз, или покупайте больше оперативной памяти.
Возможный дубликат C#: исключение нехватки памяти
@PanagiotisKanavos Не уверен, какое отношение имеет отсутствие маны к чему-либо
Требуется усиление @Matt
Как документация BatchSize объясняет Zero (the default) indicates that each WriteToServer operation is a single batch.
Эта целевая таблица находится в той же базе данных, что и исходная? В таком случае используйте команду типа INSERT INTO target SELECT * FROM source и сохраните данные на сервере.
Помимо моего ответа, обработка исключений - моя любимая мозоль. А у тебя плохо. Вы продолжаете после фатального исключения, и это последнее, что вам следует делать Когда-либо. Вы получите только более и менее понятные исключения. Вот две статьи по теме, на которую я часто ссылаюсь: blogs.msdn.microsoft.com/ericlippert/2008/09/10/… | codeproject.com/Articles/9538/…
Находится ли ваша целевая база данных на том же хосте, что и ваш C#? Если это так, потенциально существует конкуренция между двумя процессами ... Может быть, подумайте об установке максимального объема памяти для SQL, чтобы обеспечить вашему приложению некоторое пространство для маневра. docs.microsoft.com/en-us/sql/database-engine/configure-windo ws /…. Тем не менее, чисто предположение.





Что-то пошло не так в вашем дизайне, если вы даже пытаться обработали 700 тысяч строк на C#. То, что вам это не удастся, следовало ожидать.
Если это получение данных для отображения: пользователь не сможет обработать такой объем данных. А фильтрация 700 тыс. Строк в графическом интерфейсе - пустая трата времени и полосы пропускания. Сразу 25-100 полей - это почти предел. Выполните фильтрацию или разбиение на страницы на стороне запроса, чтобы в итоге вы не извлекли на порядки больше, чем вы действительно можете обработать.
Если это некоторая форма массовой вставки или массового изменения: выполняйте такую операцию в SQL Server, а не в своем коде. Получение, обработка на C# и последующая отправка просто добавляет уровни накладных расходов. Если вы добавите двухстороннюю передачу по сети, вы легко утроите это время.
Глядя на код вопроса, это второй сценарий. Но OP использует разные строки подключения для чтения и записи, поэтому, возможно, OP читает с сервера A и записывает на сервер B, что означает, что обработка данных в C# не является нелогичной.
@Stijn: Это все еще так. Просто прочтите данные сервера A с сервера B. Особенно, если это важная / массовая передача, ее не следует обрабатывать в коде C#. Выполнение кода SQL на сервере B будет намного более эффективным.
Нет никакой гарантии, что серверы A и B могут взаимодействовать напрямую. Ваш ответ не ошибочен, но я думаю, что здесь слишком много предположений, и OP должен дать некоторый контекст.
Прогнозы против, возможно, вызваны тем, что вы на самом деле не даете подробностей о том, как это сделать правильно, поэтому он читается больше как комментарий, чем ответ. И, как сказал Стейн, есть слишком много предположений относительно ограничений, с которыми сталкивается ОП. Может быть, расскажите подробнее о том, как настроить связанные серверы и примеры SQL, которые копируют строки из другой базы данных.
Я не использую поиск данных для отображения, я просто разделяю данные из одной базы данных на другую в зависимости от клиентов. Я даже пробовал приведенные выше коды решений, хотя это приводит к тому же исключению. Есть другие идеи, ребята?
@ user2302158: Я могу только повторить, что делать это на C#, вероятно, неправильно. На этой шкале (700k) практически невозможно не получить исключение или не выйти за какие-то другие ограничения. Если он работает всего лишь от 10 000 до 100 000, это, в основном, зависит от чистой удачи. Мне также интересно, почему вы думаете, что вам нужно разделить БД? Может быть, вам действительно нужны распределенные базы данных в какой-то форме?
Мое текущее требование - переместить огромное количество данных с одного сервера в распределенные базы данных за короткий период, также это должен быть автоматизированный процесс, поэтому я предпочел передать консольный пакет. Есть ли другой способ, кроме пакета, для передачи данных?
@ user2302158: Это то, что вы должны делать на самом сервере базы данных. Это чистая работа с БД. Большинство DMBS имеют надлежащую поддержку распределенных баз данных - они занимаются передачей данных. Или, по крайней мере, следует разрешить планирование для некоторого кода SQL, также известного как тот, который вы должны писать.
NB: Per Дэвид Браун ответ, кажется, я неправильно понял, как работает пакетная обработка класса SqlBulkCopy. Отредактированный код все еще может быть вам полезен, поэтому я не удалял этот ответ (поскольку код все еще действителен), но ответ заключается не в том, чтобы устанавливать BatchSize, как я думал. См. Объяснение в ответе Дэвида.
Попробуйте что-нибудь вроде этого; ключом является установка свойства Размер партии для ограничения количества строк, с которыми вы работаете одновременно:
using (SqlConnection sourceConnection = new SqlConnection(constr))
{
sourceConnection.Open();
SqlCommand commandSourceData = new SqlCommand("select * from XXXX ", sourceConnection);
using (reader = commandSourceData.ExecuteReader() { //add a using statement for your reader so you don't need to worry about close/dispose
//keep the connection open or we'll be trying to read from a closed connection
using (SqlBulkCopy bulkCopy = new SqlBulkCopy(constr2))
{
bulkCopy.BatchSize = 1000; //Write a few pages at a time rather than all at once; thus lowering memory impact. See https://docs.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlbulkcopy.batchsize?view=netframework-4.7.2
bulkCopy.DestinationTableName = "destinationTable";
try
{
// Write from the source to the destination.
bulkCopy.WriteToServer(reader);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw; //we've caught the top level Exception rather than somethign specific; so once we've logged it, rethrow it for a proper handler to deal with up the call stack
}
}
}
}
Обратите внимание: поскольку класс SqlBulkCopy принимает IDataReader в качестве аргумента, нам не нужно загружать полный набор данных. Вместо этого считыватель дает нам возможность откатывать записи по мере необходимости (следовательно, мы оставляем соединение открытым после создания считывателя). Когда мы вызываем метод SqlBulkCopyWriteToServer, у него есть внутренняя логика для многократного цикла, выбирая новые записи BatchSize из считывателя, а затем отправляя их в целевую таблицу перед повторением / завершением после того, как считыватель отправил все ожидающие записи. Это работает иначе, чем, скажем, с DataTable, где нам пришлось бы заполнить таблицу данных полным набором записей, вместо того, чтобы иметь возможность читать больше при необходимости.
Один из потенциальных рисков этого подхода заключается в том, что поскольку мы должны поддерживать соединение открытым, любые блокировки на нашем источнике остаются на месте, пока мы не закроем наш читатель. В зависимости от уровня изоляции и того, пытаются ли другие запросы получить доступ к тем же записям, это может вызвать блокировку; в то время как подход таблицы данных взял бы единовременную копию данных в память, а затем закрыл бы соединение, избегая любых блоков. Если эта блокировка вызывает беспокойство, вам следует подумать об изменении уровня изоляции вашего запроса или применении подсказок ... Тем не менее, как именно вы подойдете к этому, будет зависеть от требований.
NB: На самом деле, вместо того, чтобы запускать приведенный выше код как есть, вам нужно немного реорганизовать вещи, чтобы ограничить объем каждого метода. Таким образом, вы можете повторно использовать эту логику для копирования других запросов в другие таблицы.
Вы также можете сделать размер пакета настраиваемым, а не жестко запрограммированным, чтобы вы могли настроить значение, которое дает хороший баланс использования ресурсов и производительности (которая будет варьироваться в зависимости от ресурсов хоста).
Вы также можете использовать методы async, чтобы позволить другим частям вашей программы развиваться, пока вы ждете потока данных из / в ваши базы данных.
Вот немного измененная версия:
public Task<SqlDataReader> async ExecuteReaderAsync(string connectionString, string query)
{
SqlConnection connection;
SqlCommand command;
try
{
connection = new SqlConnection(connectionString); //not in a using as we want to keep the connection open until our reader's finished with it.
connection.Open();
command = new SqlCommand(query, connection);
return await command.ExecuteReaderAsync(CommandBehavior.CloseConnection); //tell our reader to close the connection when done.
}
catch
{
//if we have an issue before we've returned our reader, dispose of our objects here
command?.Dispose();
connection?.Dispose();
//then rethrow the exception
throw;
}
}
public async Task CopySqlDataAsync(string sourceConnectionString, string sourceQuery, string destinationConnectionString, string destinationTableName, int batchSize)
{
using (var reader = await ExecuteReaderAsync(sourceConnectionString, sourceQuery))
await CopySqlDataAsync(reader, destinationConnectionString, destinationTableName, batchSize);
}
public async Task CopySqlDataAsync(IDataReader sourceReader, string destinationConnectionString, string destinationTableName, int batchSize)
{
using (SqlBulkCopy bulkCopy = new SqlBulkCopy(destinationConnectionString))
{
bulkCopy.BatchSize = batchSize;
bulkCopy.DestinationTableName = destinationTableName;
await bulkCopy.WriteToServerAsync(sourceReader);
}
}
public void CopySqlDataExample()
{
try
{
var constr = ""; //todo: define connection string; ideally pulling from config
var constr2 = ""; //todo: define connection string #2; ideally pulling from config
var batchSize = 1000; //todo: replace hardcoded batch size with value from config
var task = CopySqlDataAsync(constr, "select * from XXXX", constr2, "destinationTable", batchSize);
task.Wait(); //waits for the current task to complete / if any exceptions will throw an aggregate exception
}
catch (AggregateException es)
{
var e = es.InnerExceptions[0]; //get the wrapped exception
Console.WriteLine(e.Message);
//throw; //to rethrow AggregateException
ExceptionDispatchInfo.Capture(e).Throw(); //to rethrow the wrapped exception
}
}
Спасибо за ответ, после вставки 200k записи в конечном итоге с тем же исключением, пожалуйста, посоветуйте мне
@ user2302158 вы можете увидеть через отладчик, в какой строке вы получаете исключение нехватки памяти? Базы данных источника или назначения работают на том же хосте, что и ваш код C#? Сколько памяти использует ваш процесс во время ошибки? Какие еще процессы конкурируют за эту память? Любая подобная информация поможет нам сузить возможные причины. заранее спасибо
Я просто использовал консольное приложение для передачи данных из одной БД в другую. Ошибка в этой строке bulkCopy.WriteToServer (reader); были, как и в ответе Дэвида Брауна, это происходит в bulkCopy.SqlRowsCopied + = (s, a) => {var mem = GC.GetTotalMemory (false); Console.WriteLine ($ "{a.RowsCopied: N0} строк скопировано. Память {mem: N0}"); };
Спасибо; и сколько памяти у вашей машины / сколько ваш процесс использует / есть ли другой процесс, который, кажется, использует больше?
Около 260 ГБ оперативной памяти, он использует только 25% памяти, и никакие другие процессы не обрабатывались одновременно.
Спасибо; так что должно быть достаточно места для маневра. Если вы можете дать ответы на другие мои вопросы выше, это все еще может помочь нам в некоторых подсказках; чем больше вы дадите нам, тем больше мы сможем вам помочь ...
Я сделаю еще одну попытку, остановив все остальные процессы, чтобы снизить уровень использования Ram и запустить консольное приложение. Благодаря этому мы сможем когда-нибудь сузить возможные причины.
заранее спасибо, я опубликовал свое использование памяти, пожалуйста, найдите его в моем сообщении
Спасибо; поэтому похоже, что вы больше не видите исключение нехватки памяти, а время ожидания. Измените bulkCopy.BulkCopyTimeout = 60 * 10; (при условии, что вы используете код Дэвида) на bulkCopy.BulkCopyTimeout = 0; (т.е. без тайм-аута: docs.microsoft.com/en-us/dotnet/api/…). В производстве вам нужно иметь какое-то значение тайм-аута, но каким должно быть это значение, должно быть осознанное решение / после того, как вы получите представление о том, сколько времени занимает операция. Предыдущее значение 10 минут довольно произвольно.
Я тоже пробовал это, но остается исключение. Нужно ли мне что-то делать на стороне Management Studio?
Когда вы говорите «исключение остается» даже с таймаутом, установленным на 0, не могли бы вы рассказать об исключениях? т.е. если мы отключили тайм-аут, мы не увидим исключения тайм-аута; так что, возможно, именно тогда мы увидим исключение OOM, но это не исключение, показанное на вашем снимке экрана.
Когда вы упоминаете «студию управления», вы имеете в виду SSMS? Где это входит в уравнение; вы запускаете какой-либо код через это, а также через приложение C#? У меня такое чувство, что нам не хватает контекста ...
пс. Вы также можете установить время ожидания команды для кода чтения равным 0. command.CommandTimeout = 0;docs.microsoft.com/en-us/dotnet/api/…
да, братан, наконец-то это сработало, я только что добавил тайм-аут команды для считываемого кода, и на этот раз использование оперативной памяти переместилось на 60%, пожалуйста, найдите снимок экрана использования оперативной памяти
Этот код не должен вызывать исключение OOM. Когда вы передаете DataReader в SqlBulkCopy.WriteToServer, вы передаете строки из источника в место назначения. Где-то еще вы храните что-то в памяти.
SqlBulkCopy.BatchSize контролирует, как часто SQL Server фиксирует строки, загруженные в место назначения, ограничивая длительность блокировки и рост файла журнала (если он не регистрируется минимально и находится в простом режиме восстановления). Независимо от того, используете вы один пакет или нет, это не должно влиять на объем памяти, используемой ни в SQL Server, ни в клиенте.
Вот пример, который копирует 10 миллионов строк без увеличения объема памяти:
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SqlBulkCopyTest
{
class Program
{
static void Main(string[] args)
{
var src = "server=localhost;database=tempdb;integrated security=true";
var dest = src;
var sql = "select top (1000*1000*10) m.* from sys.messages m, sys.messages m2";
var destTable = "dest";
using (var con = new SqlConnection(dest))
{
con.Open();
var cmd = con.CreateCommand();
cmd.CommandText = $"drop table if exists {destTable}; with q as ({sql}) select * into {destTable} from q where 1=2";
cmd.ExecuteNonQuery();
}
Copy(src, dest, sql, destTable);
Console.WriteLine("Complete. Hit any key to exit.");
Console.ReadKey();
}
static void Copy(string sourceConnectionString, string destinationConnectionString, string query, string destinationTable)
{
using (SqlConnection sourceConnection = new SqlConnection(sourceConnectionString))
{
sourceConnection.Open();
SqlCommand commandSourceData = new SqlCommand(query, sourceConnection);
var reader = commandSourceData.ExecuteReader();
using (SqlBulkCopy bulkCopy = new SqlBulkCopy(destinationConnectionString))
{
bulkCopy.BulkCopyTimeout = 60 * 10;
bulkCopy.DestinationTableName = destinationTable;
bulkCopy.NotifyAfter = 10000;
bulkCopy.SqlRowsCopied += (s, a) =>
{
var mem = GC.GetTotalMemory(false);
Console.WriteLine($"{a.RowsCopied:N0} rows copied. Memory {mem:N0}");
};
// Write from the source to the destination.
bulkCopy.WriteToServer(reader);
}
}
}
}
}
Какие выходы:
. . .
9,830,000 rows copied. Memory 1,756,828
9,840,000 rows copied. Memory 798,364
9,850,000 rows copied. Memory 4,042,396
9,860,000 rows copied. Memory 3,092,124
9,870,000 rows copied. Memory 2,133,660
9,880,000 rows copied. Memory 1,183,388
9,890,000 rows copied. Memory 3,673,756
9,900,000 rows copied. Memory 1,601,044
9,910,000 rows copied. Memory 3,722,772
9,920,000 rows copied. Memory 1,642,052
9,930,000 rows copied. Memory 3,763,780
9,940,000 rows copied. Memory 1,691,204
9,950,000 rows copied. Memory 3,812,932
9,960,000 rows copied. Memory 1,740,356
9,970,000 rows copied. Memory 3,862,084
9,980,000 rows copied. Memory 1,789,508
9,990,000 rows copied. Memory 3,903,044
10,000,000 rows copied. Memory 1,830,468
Complete. Hit any key to exit.
Спасибо, Дэвид; хороший пример / я не понял, что именно так SqlBulkCopy работает под прикрытием (хотя есть смысл подумать об этом).
Ты сможешь. OOM подходит для вашего кода, а не для SQL Server. Настройте SqlBulkCopy для отправки партии записей в цель