У меня есть код, который вставляет строки из гугл-таблиц (бизнес-требование заказчика) в MySQL таблицы. Ради увеличения скорости я делаю это через SQL INSERT(), не создавая Entity объекты.
При этом я получаю следующую ошибку на строке
$this->parseTestQuestions(...);
в том случае, если перед этим закомментировать строку с unset().
В других методах у меня нигде нет unset(), и тем не менее оно работает. Более того, если закомментировать вызов
$stmt->executeQuery();
, ошибка тоже уходит.
Текст ошибки говорит о том, что набор результатов не пустой, однако у меня нет ни одного SELECT запроса (кроме того, который получает пользователя и делается самим Symfony фреймворком ещё до моего метода) и нет ни одного курсора. Более того, у меня нет высокоуровневого способа пересоздать/закрыть соединение или вызвать close() метод на объекте Statement (такого метода не существует).
Вопрос в следующем: что происходит, является ли это нормальным, и правильное ли решение я выбрал? Почему это работает без unset(), если делать все вставки в параллельных методах (а не вызывать вспомогательные методы из основного, как я сделал здесь)? PHP или фреймворк автоматически закрывают открытые соединения с БД в конце любого метода, и вызывают unset() на всех внутренних переменных? Если да, то как технически это реализовано? Почему вообще unset этой переменной решает проблему?
private function parseTests(Google_Service_Sheets $sheetsService, string $googleFileId): array
{
$range = "'Tests'!A2:B200";
/* Получаем данные из Google Spreadsheets */
$data = $sheetsService->spreadsheets_values->get($googleFileId, $range)->getValues();
$sql = "DELETE FROM `library_test`; ALTER TABLE `library_test` AUTO_INCREMENT=0;\n";
for ($i = 0; $i < count($data); $i++) {
/* Удаление строк с недостаточным числом ячеек либо отсутствующими id,
а также с пустыми названиями */
if ($data[$i][0] === '' || count($data[$i]) < 2 || !$data[$i][1]) {
array_splice($data, $i--, 1);
continue;
}
/* Вставка данных */
$sql .= "INSERT INTO `library_test` VALUES ({$data[$i][0]}, '{$data[$i][1]}', NOW());\n";
}
/* Если у нас нет строк, то ничего делать не нужно */
if ($sql != '') {
$stmt = $this->em->getConnection()->prepare($sql);
$stmt->executeQuery();
}
/* Без этой строки мы получим ошибку SQLSTATE[HY000]: General error: 2014 */
unset($stmt);
/* Импорт вопросов к тестам */
$this->parseTestQuestions($sheetsService, $googleFileId, $data);
/* Импорт ответов на вопросы к тестам */
$this->parseTestAnswers($sheetsService, $googleFileId, $data);
return $data;
}
Вот минимальный пример ошибки на чистом PDO без Symfony и Doctrine:
<?php
class Test
{
private $conn;
public function __construct()
{
$this->conn = new PDO('mysql:dbname=test_db;host=localhost',
'login',
'password',
array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8'")
);
}
public function insertData()
{
$st = $this->conn->prepare("CREATE TABLE IF NOT EXISTS `test` (`id` INT NOT NULL, `name` VARCHAR(45) NULL, PRIMARY KEY (`id`));\nDELETE FROM `test`;\n".
"INSERT INTO `test` VALUES(1, 'A'), (2, 'B');");
$st->execute();
/* Without this line we are getting an SQL error */
$st->closeCursor();
$this->insertDataInner1();
$this->insertDataInner2();
echo "OK";
}
public function insertDataInner1()
{
$st = $this->conn->prepare("CREATE TABLE IF NOT EXISTS `test_a` (`id` INT NOT NULL, `name` VARCHAR(45) NULL, PRIMARY KEY (`id`));\nDELETE FROM `test_a`;\n".
"INSERT INTO `test` VALUES(1, 'A'), (2, 'B');");
$st->execute();
}
public function insertDataInner2()
{
$st = $this->conn->prepare("CREATE TABLE IF NOT EXISTS `test_b` (`id` INT NOT NULL, `name` VARCHAR(45) NULL, PRIMARY KEY (`id`));\nDELETE FROM `test_b`;\n".
"INSERT INTO `test` VALUES(1, 'A'), (2, 'B');");
$st->execute();
}
public function selectData()
{
$st = $this->conn->prepare("SELECT * FROM `test` WHERE `id` < :id");
$st->execute(array('id' => '100'));
$array = $st->fetchAll(PDO::FETCH_ASSOC);
print_r($array);
}
}
$obj = new Test();
$obj->insertData();
/*
This way it works just fine without closing the cursor
$obj->insertDataInner1();
$obj->insertDataInner2();
*/
?>