Как я сериализирую большой график объекта.NET в SQL Server BLOB, не создавая большой буфер?

У нас есть код как:

ms = New IO.MemoryStream
bin = New System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
bin.Serialize(ms, largeGraphOfObjects)
dataToSaveToDatabase = ms.ToArray()
// put dataToSaveToDatabase in a Sql server BLOB

Но пар памяти выделяет большой буфер от "кучи" памяти большой емкости, которая дает нам проблемы. Таким образом, как мы можем передать данные потоком, не нуждаясь в достаточном количестве свободной памяти для содержания сериализованных объектов.

Я ищу способ получить Поток от SQL-сервера, который может затем быть передан мусорному ведру. Сериализируйте () настолько избегающее хранение всех данных в моей памяти процессов.

Аналогично для чтения данных назад...


Еще некоторый фон.

Это - часть сложной числовой системы обработки, которая обрабатывает данные в псевдореальное время, ища проблемы с оборудованием и т.д., сериализация сделана для разрешения перезапуска, когда существует проблема с качеством данных от канала данных и т.д. (Мы храним подачу данных и можем повторно выполнить их после того, как оператор вырезал плохие значения.)

Поэтому мы сериализируем объект намного чаще затем, мы десериализовываем их.

Объекты, которые мы сериализируем, включают очень большие массивы главным образом, удваивается, а также много маленьких “более нормальных” объектов. Мы раздвигаем границы памяти в системе на 32 бита и заставляем коллектор гаража очень упорно работать. (Эффекты делаются в другом месте в системе улучшиться, это, например, снова использующий большие массивы скорее затем создает новые массивы.)

Часто сериализация состояния является последней соломинкой, которая бежит из исключения памяти; наше пиковое использование памяти состоит в том, в то время как эта сериализация делается.

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

Клиентское использование соединение SQL-сервера 2000, 2005 и 2008, и у нас не было бы различных путей выполнения кода для каждой версии SQL-сервера, если это возможно.

У нас может быть много активных моделей за один раз (в другом процессе, через многие машины), каждая модель может иметь много сохраненных состояний. Следовательно сохраненное состояние хранится в блобе базы данных скорее затем файл.

Поскольку распространение сохранения состояния важно, я не сериализировал бы объект в файл и затем поместил бы файл в BLOB один блок за один раз.

Другие связанные вопросы я спросил

27
задан Community 23 May 2017 в 11:47
поделиться

5 ответов

Нет встроенного функциональности ADO.NET, чтобы действительно решить это изящно для больших данных. Проблема два раза:

  • Нет API для «запись» в команду SQL или параметры в виде потока. Типы параметров, которые принимают поток (вроде FileStream ), принимают поток в считывание , который не согласен с семиантамизации семантики , напишите в поток Отказ Независимо от того, каким образом вы поверните это, вы в конечном итоге с копией памяти всего сериализованного объекта, плохого.
  • Даже если точка выше будет решена (и она не может быть), протокол TDS и то, как SQL Server принимает параметры, не работают хорошо с большими параметрами, так как весь запрос должен быть впервые получен до того, как он будет запущен в выполнение И это создаст дополнительные копии объекта внутри SQL Server.

Так что вы действительно должны подходить к этому из другого угла. К счастью, есть довольно простое решение. Хитрость состоит в том, чтобы использовать высокоэффективное обновление .write Синтаксис и пропустите кусочки данных по одному, в серии операторов T-SQL. Это рекомендуется MSDN Way, см. Модифицирование данных большого значения (MAX) данных в Ado.net . Это выглядит сложно, но на самом деле тривиально делать и подключить в класс поток.


Класс Blobstream

Это хлеб и масло раствора. Класс производного потока, который реализует метод записи в качестве вызова на синтаксис T-SQL BLOB. Прямо вперед, единственное, что интересно в том, что он должен отслеживать первое обновление, потому что обновление ... Установить BLOB.WRITE (...) Синтаксис провалится в неверном поле:

class BlobStream: Stream
{
    private SqlCommand cmdAppendChunk;
    private SqlCommand cmdFirstChunk;
    private SqlConnection connection;
    private SqlTransaction transaction;

    private SqlParameter paramChunk;
    private SqlParameter paramLength;

    private long offset;

    public BlobStream(
        SqlConnection connection,
        SqlTransaction transaction,
        string schemaName,
        string tableName,
        string blobColumn,
        string keyColumn,
        object keyValue)
    {
        this.transaction = transaction;
        this.connection = connection;
        cmdFirstChunk = new SqlCommand(String.Format(@"
UPDATE [{0}].[{1}]
    SET [{2}] = @firstChunk
    WHERE [{3}] = @key"
            ,schemaName, tableName, blobColumn, keyColumn)
            , connection, transaction);
        cmdFirstChunk.Parameters.AddWithValue("@key", keyValue);
        cmdAppendChunk = new SqlCommand(String.Format(@"
UPDATE [{0}].[{1}]
    SET [{2}].WRITE(@chunk, NULL, NULL)
    WHERE [{3}] = @key"
            , schemaName, tableName, blobColumn, keyColumn)
            , connection, transaction);
        cmdAppendChunk.Parameters.AddWithValue("@key", keyValue);
        paramChunk = new SqlParameter("@chunk", SqlDbType.VarBinary, -1);
        cmdAppendChunk.Parameters.Add(paramChunk);
    }

    public override void Write(byte[] buffer, int index, int count)
    {
        byte[] bytesToWrite = buffer;
        if (index != 0 || count != buffer.Length)
        {
            bytesToWrite = new MemoryStream(buffer, index, count).ToArray();
        }
        if (offset == 0)
        {
            cmdFirstChunk.Parameters.AddWithValue("@firstChunk", bytesToWrite);
            cmdFirstChunk.ExecuteNonQuery();
            offset = count;
        }
        else
        {
            paramChunk.Value = bytesToWrite;
            cmdAppendChunk.ExecuteNonQuery();
            offset += count;
        }
    }

    // Rest of the abstract Stream implementation
 }

Использование Blobstream

Использовать этот вновь созданный класс класса BLOB-потока в буферном потрачении . Класс имеет тривиальный дизайн, который обрабатывает только писать поток в столбец таблицы. Я повторно повторно использую таблицу с другого примера:

CREATE TABLE [dbo].[Uploads](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [FileName] [varchar](256) NULL,
    [ContentType] [varchar](256) NULL,
    [FileData] [varbinary](max) NULL)

Я добавлю купочный объект, чтобы быть сериализованным:

[Serializable]
class HugeSerialized
{
    public byte[] theBigArray { get; set; }
}

Наконец, фактическая сериализация. Сначала мы введем новую запись в таблицу загрузки , затем создайте Blobstream на вновь вставленном идентификаторе и вызовете сериализацию прямо в этот поток:

using (SqlConnection conn = new SqlConnection(Settings.Default.connString))
{
    conn.Open();
    using (SqlTransaction trn = conn.BeginTransaction())
    {
        SqlCommand cmdInsert = new SqlCommand(
@"INSERT INTO dbo.Uploads (FileName, ContentType)
VALUES (@fileName, @contentType);
SET @id = SCOPE_IDENTITY();", conn, trn);
        cmdInsert.Parameters.AddWithValue("@fileName", "Demo");
        cmdInsert.Parameters.AddWithValue("@contentType", "application/octet-stream");
        SqlParameter paramId = new SqlParameter("@id", SqlDbType.Int);
        paramId.Direction = ParameterDirection.Output;
        cmdInsert.Parameters.Add(paramId);
        cmdInsert.ExecuteNonQuery();

        BlobStream blob = new BlobStream(
            conn, trn, "dbo", "Uploads", "FileData", "Id", paramId.Value);
        BufferedStream bufferedBlob = new BufferedStream(blob, 8040);

        HugeSerialized big = new HugeSerialized { theBigArray = new byte[1024 * 1024] };
        BinaryFormatter bf = new BinaryFormatter();
        bf.Serialize(bufferedBlob, big);

        trn.Commit();
    }
}

, если вы контролируете Выполнение этого простого образец вы увидите, что нигде не является большим потоком сериализации. Образец выделит массив [1024 * 1024], но это для демонстрационных целей, чтобы иметь что-то для сериализации. Этот код сериализуется в буферизованном порядке, кусок кусочком, используя BLOB SQL Server, рекомендуемый размер обновления 8040 байтов за раз.

38
ответ дан 28 November 2019 в 04:46
поделиться

Почему бы не реализовать свою собственную систему :: IO: Производственный класс? Что позволит вам прикрепить его к столбцу SQL напрямую через UngateText для записи.

Например, (псевдо-код)

Вставить запись БД с колонкой BLOB «Инициализирован» (см. Выше updateText Статья)
Создайте свой тип потока / Ассоциировать связь БД с поток
Пройти поток к Serialize Call

Это может быть сложно вверх (множество 8040 байтов за раз, полагаю, что полагаю) вызовы к нему, и на каждом полном буфере проходят, что к DB UpdateText вызов с помощью правильного смещения.

На близком потоке вы хотите промыть все, что было оставлено, это не заполнило буфер полностью через UpdateText.

Аналогичным образом, вы можете использовать тот же / аналогичный производный поток, позволяющий считывать от столбца БД, проходя, что для десериализации.

Создание производного потока - это не все, что большая работа - я сделал это в C ++ / CLI, чтобы обеспечить взаимодействие с ISTream - и если я могу сделать это :) ... (Я могу предоставить вам C ++ / CLI Код потока, который я сделал в качестве образца, если это было бы полезным)

, если вы поместите всю работу (вставьте начальную строку, вызовы, чтобы обновить BLOB через поток) в транзакцию, вы бы избежите каких-либо потенциальных несоответствий БД, если Шаг сериализации не удается.

2
ответ дан 28 November 2019 в 04:46
поделиться

Как выглядит граф?

Одна проблема здесь - поток; Требование SQL 2005 - это боль, как иначе вы можете писать напрямую в SQLFileStrewream , однако, я не думаю, что было бы слишком сложно написать свой собственный поток . Реализация, которые буферы 8040 (или несколько нескольких) байтов и пишет его постепенно. Тем не менее, я не уверен, что стоит этой дополнительной сложности - я бы был чрезмерным искушенным, чтобы просто использовать файл в качестве буфера царапин и , затем (один раз для сериала) петлей по Файл вставки / при добавлении кусков. Я не думаю, что файловая система собирается ранить вашу общую производительность здесь, и она сэкономит, вы начинаете писать обреченные данные - I.e. Вы не говорите с базой данных, пока вы уже не знаете, какие данные вы хотите написать. Это также поможет вам минимизировать время, которое соединение открыто.

Следующая проблема - сама сериализация. Лично I не не рекомендую использовать бинарформисноформтена , чтобы написать на постоянные магазины (только для транспортировки), поскольку он является особой реализацией как в самой энкодере, так и в ваших типах (т.е. это хрупкие, если вы делаете невинные изменения в ваши данные типа).

Если ваши данные могут быть представлены достаточно как дерево (а не полный граф), я был бы очень соблазнен, чтобы попробовать буферы протокола / Protobuf-Net. Эта кодировка (разработанная Google) меньше, чем вывод вывода . Переключите платформу полностью).

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

using System;
using System.Collections.Generic;
using System.IO;
using ProtoBuf;
[ProtoContract]
public class Foo {
    private readonly List<Bar> bars = new List<Bar>();
    [ProtoMember(1, DataFormat = DataFormat.Group)]
    public List<Bar> Bars { get { return bars;}}
}
[ProtoContract]
public class Bar {
    [ProtoMember(1)]
    public int Id { get; set; }
    [ProtoMember(2)]
    public string Name { get; set; }
}
static class Program {
    static void Main() {
        var obj = new Foo { Bars = {
            new Bar { Id = 123, Name = "abc"},
            new Bar { Id = 456, Name = "def"},
        } };
        // write it and show it
        using (MemoryStream ms = new MemoryStream()) {
            Serializer.Serialize(ms, obj);
            Console.WriteLine(BitConverter.ToString(ms.ToArray()));
        }
    }
}

Примечание: я делаю иметь Теории о том, как взломать формат провода Google для поддержки полных графов, но вам нужно будет понадобиться некоторое время, чтобы попробовать. О, Re «Очень большие массивы» - для примитивных типов (не объектов) ЮО могут использовать «упакованный» кодировкой для этого; [DataMember (..., Опции = элементымириализацияОтриизация.] - может быть полезно , но трудно сказать без видимости вашей модели.

4
ответ дан 28 November 2019 в 04:46
поделиться

Я бы пошел с файлами. В основном используйте файловую систему в качестве промежуточного соединения между SQL Server и вашим приложением.

  1. При сериализации большого объекта сериализуйте его в Filestream .
  2. Чтобы импортировать его в базу данных, поручить базу данных использовать файл непосредственно при сохранении данных. Вероятно, будет выглядеть что-то вроде этого:

    вставить в мышливый ([MyColumn]) Выберите B.BULKCOLUMN, от OpenRowset (Bulk N'C: \ Путь к моему файлу \ file.ext ', Single_blob) Как B

  3. При чтении данных поручает SQL сохранить большую столбцу обратно в файловую систему В качестве временного файла, который вы будете удалять после десериализации его в память (не нужно его немедленно удалять, как можно сделать кэширование здесь). Не совсем уверен, что такое команда SQL для этого, так как я наверняка, нет экспертов DB, ​​но я уверен, что должен быть один.

  4. Снова используя FileStream объект для десертилизации его обратно в память.

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

1
ответ дан 28 November 2019 в 04:46
поделиться

Вы всегда можете написать на SQL Server на более низком уровне, используя сверхпроводной протокол TDS (табличный поток данных), который компания Microsoft использует с первого дня. Они вряд ли изменят его в ближайшее время, так как даже SQLAzure использует его!

Вы можете посмотреть исходные тексты того, как это работает, из проекта Mono и из проекта freetds

Посмотрите tds_blob

4
ответ дан 28 November 2019 в 04:46
поделиться
Другие вопросы по тегам:

Похожие вопросы: