День добрый.
Необходимо перетащить некоторые данные из текстового файла в таблицу базы данных MySQL(не менее 5000 строк в таблицу).
Следующая процедура для теста - в цикле вставляет в таблицу записи и успешно завершается.
Процедура завершена, а запись в базу данных на самом деле продолжается - как отследить этот процесс ?
Пробовал через скрипт по событию "Выполнен SQL запрос" выводить в поле на экран текущий aQuery - отображает последний запрос, а по факту запись не выполнена.
Запись в БД продолжается вплоть до остановки проекта - SCADA кэширует запросы ?
Закрываем скаду, переходим в Workbench и видим что записаны не все данные.
Подскажите как надо правильно делать.
var
aQuery, text: string;
i: integer;
begin
aQuery := 'CREATE TABLE IF NOT EXISTS '+ 'Test_Data' +' (' +
'`ID` INT UNSIGNED NOT NULL AUTO_INCREMENT,' +
'`Test_string` CHAR(32),'+
'PRIMARY KEY (`ID`)' +
') ENGINE = InnoDB DEFAULT CHARACTER SET = cp1251;';
RunSQL(aQuery, nil, 0);
for i := 1 to 10000 do
begin
text := QuotedStr('Цикл => '+inttostr(i));
aQuery := 'INSERT INTO `Test_Data` ' +
'(`Test_string`) VALUES ' +
'('+ text +');';
RunSQL(aQuery, nil, 1);
end;
end.
Здравствуйте.
Процедура RunSQL просто добавляет запрос в очередь запросов. Затем запросы последовательно берутся из очереди и отправляются на выполнение в СУБД. А выполнять запросы СУБД может сколько угодно, скорость выполнения зависит от конфигурации ПК, конфигурации СУБД, свободной оперативной памяти, сложности запроса и т.д.
В приведенном коде Вы посылаете отдельный запрос на вставку каждой строки и не удивительно, что эти запросы долго выполняются. Вместо этого нужно группировать строки в один запрос используя соответствующий синтаксис заложенный MySQL, например:
aQuery := 'INSERT INTO `tbl_name` (a, b, c) VALUES(1, 2, 3), (4, 5, 6), (7, 8, 9);';
, где tbl_name - имя таблицы в которую идёт вставка, a,b,c - колонки. Далее перечисление значений, которые нужно вставить. В данном примере производится вставка сразу трёх "строк". Подробнее про синтаксис можно прочесть на официальном сайте MySQL (https://dev.mysql.com/doc/refman/8.0/en/insert.html). Также нужно учитывать, что есть ограничение на длину запроса и один запрос на все 10000 записей может оказаться слишком длинным, поэтому лучше группировать не все строки сразу, а группами, например по 500 штук.
Подскажите как надо правильно делать.
А зачем выполнять вставку данных из файла в БД через скаду? Это очень нестандартное решение для заполнения БД и нестандартное использование скады. Может быть лучше воспользоваться функцией импорта через MySQLWorkbench (см. вложение)?
Событие "Выполнен SQL запрос" это действительно признак завершения выполнения запроса и для его обработки требуется скрипт соответствующего типа.
В Вашем примере создается очередь из 10000 запросов без ожидания завершения выполнения предыдущего запроса и ошибки, предположительно, возникают из-за ограничений системы (максимальное допустимое время выполнения скрипта, превышение допустимой длины очереди запросов и т.д.).
Для выполнения запросов "поодиночке" Ваш пример можно изменить так:
1. Начало процесса:
var
aQuery: string;
begin
aQuery := 'CREATE TABLE IF NOT EXISTS '+ 'Test_Data' +' (' +
'`ID` INT UNSIGNED NOT NULL AUTO_INCREMENT,' +
'`Test_string` CHAR(32),'+
'PRIMARY KEY (`ID`)' +
') ENGINE = InnoDB DEFAULT CHARACTER SET = cp1251;';
RunSQL(aQuery, nil, 1);
end.
2. Выполняемый в цикле скрипт типа "Выполнен SQL запрос":
var
aQuery, text: string;
i: integer;
begin
i := DataSet.Tag;
if i >= 10000 then exit; // выход из цикла
text := QuotedStr('Цикл => '+inttostr(i));
aQuery := 'INSERT INTO `Test_Data` ' +
'(`Test_string`) VALUES ' +
'('+ text +');';
RunSQL(aQuery, nil, i + 1);
Text1.Text := text; // индикация хода процесса
end.
Пример во вложении.
P.S. В Вашей задаче разумно использовать на одну операцию один SQL запрос и записывать по 22 параметра.
Получается скрипт по событию "Выполнен SQL запрос" - на самом деле "SQL запрос помещен в очередь".
Нет, событие "Выполнен SQL запрос" вызывается когда SQL-запрос выполнен полностью и СУБД вернула ответ о том, что запрос выполнен. Если бы это было добавление в очередь, то мы бы так и назвали событие. В целом все существующие события сейчас названы именно так, как они работают.
Технически возможно ли создать настоящее событие "Выполнен SQL запрос" - т.е. ушел в СУБД, что бы можно было реально отследить процесс?
Да, событие "Выполнен SQL запрос" это и есть настоящее выполнение SQL-запроса.
Пробовал через скрипт по событию "Выполнен SQL запрос" выводить в поле на экран текущий aQuery - отображает последний запрос, а по факту запись не выполнена.
Вывести SQL-код текущего SQL-запроса (текущий aQuery) из скрипта с событием "Выполнен SQL запрос" - невозможно. Видимо Вы просто вывели на экран код запроса отправленного в очередь и конечно после выполнения цикла он будет равен коду последнего запроса. Если Вы хотите увидеть прогресс выполнения запросов, то нужно выводить информацию на экран из скрипта "Выполнен SQL запрос", как выше написал pan2000. В таком скрипте можно получить только Tag и Sender-объект присвоенные запросу во время отправки в очередь. Например:
Скрипт запускает выполнение 100 запросов, каждый запрос помечается уникальным тегом:
var
I: Integer;
aQuery: string;
begin
for I := 0 to 100 do
begin
aQuery := 'INSERT INTO `test`(`value`) VALUES (' + IntToStr(I) + ');';
RunSQL(aQuery, nil, I); // в качестве тега запроса передаем значение переменной I
end;
end.
Скрипт "Выполнен SQL-запрос":
begin
Text1.Text := IntToStr(DataSet.Tag); // выводим Tag выполненного SQL-запроса в объект Text1
end.
Вариант, предложенный pan2000 тоже рабочий, но учитывайте что он будет работать медленнее, т.к. новый запрос будет добавляться в очередь только после выполнения предыдущего. Поэтому все-таки лучше отправлять в очередь сразу все запросы.
Всем спасибо за ваши советы.
По рекомендации Simple-Scada сгруппировал набор данных и теперь запись в СУБД одновременно заканчивается с декодированием файла, т.е. сразу же открываю Workbench и вижу что все данные записаны.
Сгруппировал по 20 деталей (в каждой разное количество процессов) - получается около 300 строк (31 столбец не считая ID).
upd: Добавил для каждого запроса уникальный тег + флаг соответствующей операции, в результате теперь можно отслеживать прогресс, а группировку записи деталей уменьшил пока до 2 чтобы было время наблюдать прогрессбар (около 10 сек.):
Скрипт "Выполнен SQL-запрос":
var
i: Integer;
begin
i := DataSet.Tag;
if flag_import_to_db.Value then // соответствующий флаг
begin
progress_DB.Value := int ((i/count_Query_Insert.Value)*100); // шкала прогрессбар в %
if i = count_Query_Insert.Value then
begin
flag_import_to_db.Value := false;
Status_import.FlashColor := clNone;
Status_import.Color := clDarkGreen;
Status_import.Text := 'Обработка завершена.';
Button_ImportToDB.Enabled := true;
end;
end;
end.