11 июня 2012

Хранение массива в BLOB-поле

    Понадобилось мне хранить результаты измерений в базе данных. Просто сохранить их в таблицу не получится, т.к. количество строк с информацией в одном измерении быстро перевалит за миллион, и при сохранении их в одной транзакции пользователь успеет попить кофе. Плодить множество мелких транзакций по несколько тысяч записей тоже не сильно прибавит скорости. Скорость при последующей вычитке из базы данных миллионов строк тоже будет печальной. Поэтому единственный очевидный для меня выход - это сохранить результаты измерения в поле типа BLOB. Измерения у меня хранились в многомерном массиве, но я упрощу код и объявлю динамический одномерный массив целых чисел:

Var
  aData: Array of Integer;

Процедура записи динамического массива в базу данных получилась примерно такая:

Var
  ms: TMemoryStream;
  ...
begin
  ...
  Try
    ms := TMemoryStream.Create;
    // Запись содержимого массива aData в поток ms
    ms.WriteBuffer(aData[0], Length(aData) * SizeOf(aData[0]));
    // Запись содержимого потока ms в параметр query для вставки данных
    qInsertResearch.Params[5].LoadFromStream(ms, ftBlob);
  Finally
    ms.Free;
  End;
  ...

А процедура чтения массива из базы данных не сложнее процедуры записи:

Var
  ms: TMemoryStream;
  ...
begin
  ...
  Try
    ms := TMemoryStream.Create;
    // Запись содержимого поля qResearchDATA типа TBlobField в поток ms
    qResearchDATA.SaveToStream(ms);
    // Установка размера динамического массива aData
    SetLength(aData, ms.Size div SizeOf(aData[0]));
    ms.Position := 0;
    // Запись содержимого потока ms в массив aData
    ms.ReadBuffer(aData[0], ms.Size);
  Finally
    ms.Free;
  End;
  ...

Теперь на запись/чтение нескольких миллионов записей моя программа тратит секунду. Вместо размера первого элемента массива SizeOf(aData[0]) вы можете использовать размер типа элементов массива (для приведенного мной примера - SizeOf(Integer)). Но такой вариант кода будет менее универсальным, т.к. будет зависеть от используемого типа данных.

P.S. При использовании такого способа хранения данных не забывайте, что размер некоторых типов данных зависит от операционной системы, для которой скомпилирована программа. Иначе информация, которую прочтет из базы данных ваша 64-битная программа перекомпилированная из 32-битной, вас неприятно удивит.

Продолжение...

4 комментария:

  1. "4" в коде напрягает. Пост может читать молодежь, которая скопипастит исходник.
    Пожалуйста, ввиду лёгко-доступности 64 битного кода в Delphi XE2, а также целесообразности именно универсализации кода относительно платформы, поправьте публикацию. Тем более, что Вы это и так делаете в последнем абзаце.

    ОтветитьУдалить
  2. Согласен - спасем копипастеров :) Переделал код и концовку.

    ОтветитьУдалить
  3. А не проще было бы сделать без MemoryStream-a. что-то типа:

    var
    tmpBlobStream: TStream;

    begin
    ...
    tmpBlobStream = Dataset.CreateBlobStream(...);
    tmpBlobStream .WriteBuffer(aData[0], Length(aData) * SizeOf(aData[0]))
    Dataset.Post;

    ОтветитьУдалить
  4. Если бы я редактировал DataSet, то можно и так. Но я же задаю значение не полю, а параметру, т.к. для добавления записей в базу данных я использую Query с параметрами (insert into x (..., DATA) values(..., :DATA)). Но без TMemoryStream действительно можно обойтись и мой код можно значительно упростить - http://it-blackcat.blogspot.com/2012/06/blob-2.html
    Спасибо за мысль!

    ОтветитьУдалить