@popov654
Специалист в области веб-технологий

Почему возникает SQLSTATE[HY000]: General error: 2014 при прямой вставке данных в MySQL таблицы через DBAL?

У меня есть код, который вставляет строки из гугл-таблиц (бизнес-требование заказчика) в 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();

*/

?>
  • Вопрос задан
  • 342 просмотра
Решения вопроса 1
ipatiev
@ipatiev Куратор тега PHP
Потомок старинного рода Ипатьевых-Колотитьевых
Потому что при работе через API запросы должны выполняться по одному, а не кучей.
Все эти точечки с запятой предназначены для консоли, а через API с мультизапросами всегда будут проблемы (даже если у какого-то умника в его песочнице с куличиками проблем до сих пор не было).
Потому что API предназначено для выполнения запросов по одному.

Тем более что здесь нужно всего три запроса, а не 100500. Множественная вставка делается не так, а одним запросом. При этом, как нас учат в первом классе, запросы должны быть параметризованными а не вот это вот всё.
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

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