Этот пост об удобной возможности FireDAC писать в базу сразу очень много записей. В документации указано, что эта техника позволит сократить количество соединений между базой и нашим приложением. Для начала в БД MySQL создадим таблицу с одним автоинкрементным полем-счетчиком и 5 одинаковыми полями. Для этого я воспользовался услугами программы Workbench. Ниже я привожу код создания БД, его можно просто скопировать и вставить в консоль MySQL.
Создание БД и таблицы
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0; SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0; SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL,ALLOW_INVALID_DATES'; CREATE SCHEMA IF NOT EXISTS `array_db` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci ; USE `array_db` ; -- ----------------------------------------------------- -- Table `array_db`.`firstTable` -- ----------------------------------------------------- DROP TABLE IF EXISTS `array_db`.`firstTable` ; CREATE TABLE IF NOT EXISTS `array_db`.`firstTable` ( `PK` INT NOT NULL AUTO_INCREMENT , `field1` VARCHAR(45) NOT NULL , `field2` VARCHAR(45) NOT NULL , `field3` VARCHAR(45) NOT NULL , `field4` VARCHAR(45) NOT NULL , `field5` VARCHAR(45) NOT NULL , PRIMARY KEY (`PK`) ) ENGINE = InnoDB; USE `array_db` ; SET SQL_MODE=@OLD_SQL_MODE; SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS; SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS; |
Итак, наша БД называется array_db, а таблица в ней называется firsttable.
Создание клиента Delphi
Создадим новый VCL проект и добавим следующие компоненты
Для FDConnection1 создадим отдельное определение Connection Definition под именем MySQL_array_db и подключим его, настроим следующим образом. Это можно сделать разными способами, я делал через View | DataExplorer | FireDac | MySQL Server –> правой кнопкой мыши Add Connection и далее настроил параметры
В FDQuery1 в свойстве SQL я написал запрос select*from firsttable
Генерация 1000 000 записей
Итак, сейчас мы перейдем непосредственно к генерации 1000 000 записей. Саму генерацию записей я предлагаю убрать в отдельный поток, поскольку миллион записей не появятся в базе одномоментно, а главное приложение может понадобиться.
Создание потока…
1 2 3 4 5 6 7 |
type ... TDBThread = class(TThread) protected procedure Execute; override; end; ... |
Далее ставим курсор на procedure Execute; и жмем Ctrl+Shift+C и получаем генерацию шаблона процедуры, в которую добавляем следующий код
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
procedure TDBThread.Execute; var i,ArraySize:integer; begin inherited; ArraySize:=strtoint(Form1.Edit1.Text); //Включаем таймер seconds:=0; form1.Timer1.Enabled:=true; //Чистим таблицу Form1.FDQuery1.SQL.text:='delete from firsttable'; Form1.FDQuery1.ExecSQL; //Вставляем в таблицу записи Form1.FDQuery1.SQL.text:= ( 'insert into firsttable (PK,field1,field2,field3,field4,field5)'+ ' values (:p1,:p2,:p3,:p4,:p5,:p6)' ); Form1.FDQuery1.Params.ArraySize := ArraySize; for i := 0 to ArraySize-1 do begin Form1.FDQuery1.Params[0].AsIntegers[i] := i+1; Form1.FDQuery1.Params[1].AsStrings[i] := 'Record'; Form1.FDQuery1.Params[2].AsStrings[i] := 'Record'; Form1.FDQuery1.Params[3].AsStrings[i] := 'Record'; Form1.FDQuery1.Params[4].AsStrings[i] := 'Record'; Form1.FDQuery1.Params[5].AsStrings[i] := 'Record'; end; Form1.FDQuery1.Execute(ArraySize, 0); //Отключаем таймер form1.Timer1.Enabled:=false; ShowMessage('Rows Affected ='+IntToStr(Form1.FDQuery1.RowsAffected)+ #13#10+'time = '+inttostr(seconds)+' sec.' ); //Заново открываем множество Form1.FDQuery1.Open('select * from firsttable'); end; |
Таймер
Таймер нужен для того, чтобы оценить время операции, поскольку мы будем работать с большими массивами – ограничимся секундами
1 2 3 4 5 6 7 8 9 |
... var seconds:integer; // среди глобальных переменных добавляем ... procedure TForm1.Timer1Timer(Sender: TObject); begin seconds:=seconds+1; Form1.Caption:='Time ='+inttostr(seconds)+' sec.'; end; |
Кнопка generateArray
1 2 3 4 5 6 |
procedure TForm1.generateArrayClick(Sender: TObject); var ArrayThread:TDBThread; begin ArrayThread:=TDBThread.Create(False); ArrayThread.FreeOnTerminate:=true; end; |
Кнопка clearArray
1 2 3 4 |
procedure TForm1.clearArrayClick(Sender: TObject); begin FDQuery1.SQL.Text:='delete from firsttable'; FDQuery1.ExecSQL; end; |
Результат
Улучшаем результат
Результат для 1000 000 добавленных записей не плохой, но и его можно улучшить, поиграв с параметром Resource Options.ArrayDMLSize, который по умолчанию равен 2147483647 или 2 GB, но максимальное ограничение параметра max_allowed_packet по базе MySQL составляет 1GB, поэтому мы можем экспериментировать со снижением этого параметра. Выставим его, например в значение 10485760 или 10 Мб и посмотрим как это повлияет на время.
Поставим значение Resource Options.ArrayDMLSize в 104857600 или 100 Мб и получим результат
Настраивая различные параметры со стороны сервера БД и клиента можно уменьшить время ожидания операций. Но это процесс экспериментов.
Выводы
На мой взгляд, для поставленной задачи результат очень хороший. По сути мы записали 1 миллион записей всего за 22 секунды, думаю, результат можно ещё улучшать за счет различных настроек, но это логично делать на рабочей задаче, а не на учебной.Представленная реализация далеко не единственная, но, на мой взгляд довольно простая и эффективная. В документации описываются ещё примеры.
Чем мне нравится FireDac, что при открытии множества, в DBGrid он загружает не весь 1000 000 записей, а FDQuery.FetchOptions.RowsetSize, по умолчанию там 50 записей. Это сильно увеличивает скорость отображения и экономит оперативную память.