Підтримка PDO для декількох запитів (PDO_MYSQL, PDO_MYSQLND)


102

Я знаю, що PDO не підтримує виконання декількох запитів в одному операторі. Я працював у Googleing і знайшов кілька публікацій про PDO_MYSQL та PDO_MYSQLND.

PDO_MySQL - більш небезпечна програма, ніж будь-які інші традиційні програми MySQL. Традиційний MySQL дозволяє лише один запит SQL. У PDO_MySQL немає такого обмеження, але ви ризикуєте ввести кілька запитів.

Від: Захист від ін'єкції SQL за допомогою PDO та Zend Framework (червень 2010; Julian)

Схоже, PDO_MYSQL і PDO_MYSQLND забезпечують підтримку декількох запитів, але я не в змозі знайти більше інформації про них. Чи були припинені ці проекти? Чи є зараз спосіб запустити кілька запитів за допомогою PDO.


4
Використовуйте транзакції SQL.
tereško

Чому ви хочете використовувати кілька запитів? Вони не здійснюють транзакцій, це так само, як ви б їх страчували один за одним. ІМХО ніяких плюсів, лише мінуси. У випадку SQLInjection ви дозволяєте зловмиснику робити все, що завгодно.
млеко

Зараз 2020 рік, і PDO це підтримує - дивіться мою відповідь нижче.
Андріс

Відповіді:


141

Як я знаю, PDO_MYSQLNDзамінено PDO_MYSQLна PHP 5.3. Заплутаною частиною є те, що ім’я все ще є PDO_MYSQL. Тож тепер ND є драйвером за замовчуванням для MySQL + PDO.

Загалом, для виконання декількох запитів відразу вам потрібно:

  • PHP 5.3+
  • mysqlnd
  • Емульовані підготовлені заяви. Переконайтеся, що PDO::ATTR_EMULATE_PREPARESвстановлено значення 1(за замовчуванням). Крім того, ви можете уникати використання підготовлених заяв та використання $pdo->execбезпосередньо.

Використання exec

$db = new PDO("mysql:host=localhost;dbname=test", 'root', '');

// works regardless of statements emulation
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0);

$sql = "
DELETE FROM car; 
INSERT INTO car(name, type) VALUES ('car1', 'coupe'); 
INSERT INTO car(name, type) VALUES ('car2', 'coupe');
";

$db->exec($sql);

Використання висловлювань

$db = new PDO("mysql:host=localhost;dbname=test", 'root', '');

// works not with the following set to 0. You can comment this line as 1 is default
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 1);

$sql = "
DELETE FROM car; 
INSERT INTO car(name, type) VALUES ('car1', 'coupe'); 
INSERT INTO car(name, type) VALUES ('car2', 'coupe');
";

$stmt = $db->prepare($sql);
$stmt->execute();

Нотатка:

Під час використання емульованих підготовлених операторів переконайтеся, що ви встановили правильне кодування (яке відображає фактичне кодування даних) у DSN (доступне з 5.3.6). Інакше може бути незначна можливість для ін'єкцій SQL, якщо використовується якесь непарне кодування .


37
У самій відповіді немає нічого поганого. Це пояснює, як виконати кілька запитів. Ваше припущення, що відповідь є помилковим, випливає з припущення, що запит містить введення користувача. Є дійсні випадки використання, коли надсилання через декілька запитів одночасно може принести користь продуктивності. Ви можете запропонувати використовувати процедури як альтернативну відповідь на це питання, але це не робить цю відповідь поганою.
Gajus

9
Код у цій відповіді поганий і сприяє деяким дуже шкідливим практикам (використання емуляції для підготовки операторів, які роблять код відкритим для вразливості ін'єкцій SQL ). Не використовуйте.
tereško

17
Нічого поганого в цій відповіді, зокрема в режимі емуляції. Він увімкнено за замовчуванням у pdo_mysql, і якби виникла якась проблема, вже було б тисячі ін'єкцій. Але ще ніхто не наближається до цього. Тому вона йде.
Твій здоровий глузд

3
Фактично, ircmaxell був лише той, кому вдалося надати не лише емоції, а й якийсь аргумент. Однак посилання, які він привів, зовсім не мають значення. По-перше, це взагалі не застосовується, оскільки прямо говориться, що "PDO завжди захищений від цієї помилки". Тоді як другий легко вирішується, встановивши правильне кодування. Отже, вона заслуговує на замітку, а не попередження та менш привабливу.
Твій здоровий глузд

6
Виступаючи як хтось, хто пише інструмент міграції, що використовує SQL, який написали лише наші розробники (тобто введення SQL - це не проблема), це мені дуже допомогло, і будь-які коментарі, що свідчать про шкідливість цього коду, не повністю розуміють усі контексти для його використання.
Лука

17

Половину дня поспілкувавшись з цим, з’ясував, що в PDO помилка, де ...

-

//This would run as expected:
$pdo->exec("valid-stmt1; valid-stmt2;");

-

//This would error out, as expected:
$pdo->exec("non-sense; valid-stmt1;");

-

//Here is the bug:
$pdo->exec("valid-stmt1; non-sense; valid-stmt3;");

Це виконає "valid-stmt1;", зупиниться "non-sense;"і ніколи не видасть помилку. Не запустити "valid-stmt3;", повернути правду і брехати, що все пройшло добре.

Я б очікував, що помилка вийде, "non-sense;"але це не так.

Ось де я знайшов цю інформацію: Недійсний запит PDO не повертає помилку

Ось помилка: https://bugs.php.net/bug.php?id=61613


Отже, я спробував це зробити з mysqli і насправді не знайшов твердої відповіді про те, як це працює, тому я подумав, що просто залиште це для тих, хто хоче ним скористатися ..

try{
    // db connection
    $mysqli = new mysqli("host", "user" , "password", "database");
    if($mysqli->connect_errno){
        throw new Exception("Connection Failed: [".$mysqli->connect_errno. "] : ".$mysqli->connect_error );
        exit();
    }

    // read file.
    // This file has multiple sql statements.
    $file_sql = file_get_contents("filename.sql");

    if($file_sql == "null" || empty($file_sql) || strlen($file_sql) <= 0){
        throw new Exception("File is empty. I wont run it..");
    }

    //run the sql file contents through the mysqli's multi_query function.
    // here is where it gets complicated...
    // if the first query has errors, here is where you get it.
    $sqlFileResult = $mysqli->multi_query($file_sql);
    // this returns false only if there are errros on first sql statement, it doesn't care about the rest of the sql statements.

    $sqlCount = 1;
    if( $sqlFileResult == false ){
        throw new Exception("File: '".$fullpath."' , Query#[".$sqlCount."], [".$mysqli->errno."]: '".$mysqli->error."' }");
    }

    // so handle the errors on the subsequent statements like this.
    // while I have more results. This will start from the second sql statement. The first statement errors are thrown above on the $mysqli->multi_query("SQL"); line
    while($mysqli->more_results()){
        $sqlCount++;
        // load the next result set into mysqli's active buffer. if this fails the $mysqli->error, $mysqli->errno will have appropriate error info.
        if($mysqli->next_result() == false){
            throw new Exception("File: '".$fullpath."' , Query#[".$sqlCount."], Error No: [".$mysqli->errno."]: '".$mysqli->error."' }");
        }
    }
}
catch(Exception $e){
    echo $e->getMessage(). " <pre>".$e->getTraceAsString()."</pre>";
}

Це працює, якщо ви працюєте лише $pdo->exec("valid-stmt1; non-sense; valid-stmt3;");без попередніх двох виконань? Я можу змусити це викидати помилки посередині, але не тоді, коли виконується після успішних виконання .
Джефф Пукетт

Ні, це не так. Ось помилка з PDO.
Sai Phaninder Reddy J

1
Моє погано, ці 3 $pdo->exec("")незалежні один від одного. Тепер я розбила їх, щоб вказати, що вони не повинні мати послідовність для виникнення проблеми. Ці 3 - це 3 конфігурації запуску декількох запитів в одному операторі exec.
Sai Phaninder Reddy J

Цікаво. Ви отримали шанс побачити моє розміщене запитання? Цікаво, чи це частково зафіксовано, тому що я можу отримати помилку, викинуту, якщо вона є єдиною execна сторінці, але якщо я запускаю декілька execз декількома операторами SQL в них, то я відтворюю ту саму помилку. Але якщо це єдине execна сторінці, я не можу його відтворити.
Джефф Пукетт

Чи execмав це на вашій сторінці кілька заяв?
Sai Phaninder Reddy J

3

Швидкий і брудний підхід:

function exec_sql_from_file($path, PDO $pdo) {
    if (! preg_match_all("/('(\\\\.|.)*?'|[^;])+/s", file_get_contents($path), $m))
        return;

    foreach ($m[0] as $sql) {
        if (strlen(trim($sql)))
            $pdo->exec($sql);
    }
}

Розбивається на розумні кінцеві точки оператора SQL. Немає перевірки помилок, захисту від ін'єкцій. Розумійте своє використання перед його використанням. Особисто я використовую його для висіву необроблених файлів міграції для тестування інтеграції.


1
Це не вдається, якщо ваш файл SQL містить будь-які вбудовані команди mysql ... Це, ймовірно, також підірве ваш ліміт пам’яті PHP, якщо файл SQL великий… Розщеплення на ;перерви, якщо ваш SQL містить процедури або визначення тригера ... Багато причини, чому це не добре.
Білл Карвін

1

Як і тисячі людей, я шукаю це запитання:
можу запускати кілька запитів одночасно, і якщо була одна помилка, жодна не запустилася, я перейшов на цю сторінку скрізь.
Але хоча друзі тут дали хороші відповіді, ці відповіді не були гарними моя проблема
Тому я написав функцію, яка працює добре і майже не має проблем з sql Injection.
Це може бути корисно для тих, хто шукає подібні запитання, тому я ставлю їх для використання

function arrayOfQuerys($arrayQuery)
{
    $mx = true;
    $conn->beginTransaction();
    try {
        foreach ($arrayQuery AS $item) {
            $stmt = $conn->prepare($item["query"]);
            $stmt->execute($item["params"]);
            $result = $stmt->rowCount();
            if($result == 0)
                $mx = false;
         }
         if($mx == true)
             $conn->commit();
         else
             $conn->rollBack();
    } catch (Exception $e) {
        $conn->rollBack();
        echo "Failed: " . $e->getMessage();
    }
    return $mx;
}

для використання (приклад):

 $arrayQuery = Array(
    Array(
        "query" => "UPDATE test SET title = ? WHERE test.id = ?",
        "params" => Array("aa1", 1)
    ),
    Array(
        "query" => "UPDATE test SET title = ? WHERE test.id = ?",
        "params" => Array("bb1", 2)
    )
);
arrayOfQuerys($arrayQuery);

і моє з'єднання:

    try {
        $options = array(
            //For updates where newvalue = oldvalue PDOStatement::rowCount()   returns zero. You can use this:
            PDO::MYSQL_ATTR_FOUND_ROWS => true
        );
        $conn = new PDO("mysql:host=$servername;dbname=$database", $username, $password, $options);
        $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    } catch (PDOException $e) {
        echo "Error connecting to SQL Server: " . $e->getMessage();
    }

Примітка.
Це рішення допомагає запускати декілька операторів разом.
Якщо трапляється неправильний оператор, він не виконує жодного іншого оператора


0

Спробував наступний код

 $db = new PDO("mysql:host={$dbhost};dbname={$dbname};charset=utf8", $dbuser, $dbpass, array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));

Тоді

 try {
 $db->query('SET NAMES gbk');
 $stmt = $db->prepare('SELECT * FROM 2_1_paidused WHERE NumberRenamed = ? LIMIT 1');
 $stmt->execute(array("\xbf\x27 OR 1=1 /*"));
 }
 catch (PDOException $e){
 echo "DataBase Errorz: " .$e->getMessage() .'<br>';
 }
 catch (Exception $e) {
 echo "General Errorz: ".$e->getMessage() .'<br>';
 }

І отримав

DataBase Errorz: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '/*' LIMIT 1' at line 1

Якщо додано $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);після$db = ...

Потім отримали порожню сторінку

Якщо замість цього SELECTспробувати DELETE, то в обох випадках з'явилася помилка

 DataBase Errorz: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '* FROM 2_1_paidused WHERE NumberRenamed = '¿\' OR 1=1 /*' LIMIT 1' at line 1

Тож мій висновок, що жодна ін'єкція неможлива ...


3
Вам слід було поставити нове запитання, де згадується цей
Ваш здоровий глузд

Не стільки питання, як результат того, що я спробував. І мій висновок. Початкове запитання є старим, можливо, наразі не актуальним.
Андріс

Не впевнений, наскільки це стосується будь-якого питання.
cHao

під питанням є слова but you risk to be injected with multiple queries.Моя відповідь про ін'єкцію
Андріс

0

Спробуйте скористатися цією функцією: кілька запитів і вставка декількох значень.

function employmentStatus($Status) {
$pdo = PDO2::getInstance();

$sql_parts = array(); 
for($i=0; $i<count($Status); $i++){
    $sql_parts[] = "(:userID, :val$i)";
}

$requete = $pdo->dbh->prepare("DELETE FROM employment_status WHERE userid = :userID; INSERT INTO employment_status (userid, status) VALUES ".implode(",", $sql_parts));
$requete->bindParam(":userID", $_SESSION['userID'],PDO::PARAM_INT);
for($i=0; $i<count($Status); $i++){
    $requete->bindParam(":val$i", $Status[$i],PDO::PARAM_STR);
}
if ($requete->execute()) {
    return true;
}
return $requete->errorInfo();
}

0

PDO підтримує це (станом на 2020 рік). Просто виконайте виклик query () на об'єкт PDO, як зазвичай, розділяючи запити на; а потім nextRowset (), щоб перейти до наступного результату SELECT, якщо у вас їх кілька. Набори результатів будуть у тому ж порядку, що і запити. Очевидно, подумайте про наслідки для безпеки - тому не приймайте запити, що надаються користувачем, використовуйте параметри тощо. Я використовую це, наприклад, із запитами, згенерованими кодом.

$statement = $connection->query($query);
do {
  $data[] = $statement->fetchAll(PDO::FETCH_ASSOC);
} while ($statement->nextRowset());

Я ніколи б не зрозумів такого роду міркувань: "Ось код, який є великою дірою в безпеці, ігноруючи всі рекомендовані добрі практики, тому вам потрібно думати про наслідки для безпеки". Хто повинен про це думати? Коли вони повинні задуматися - до використання цього коду чи після того, як вони будуть зламані? Чому ви не подумаєте про це спочатку, перш ніж написати цю функцію чи запропонувати її іншим людям?
Твій здоровий глузд

Шановний @YourCommonSense, який виконує декілька запитів за один раз, допомагає в роботі, менше мережевий трафік + сервер може оптимізувати відповідні запити. Мій (спрощений) приклад мав на увазі лише ввести метод, необхідний для його використання. Це безпека лише в тому випадку, якщо ви не використовуєте тих передових практик, про які ви посилаєтесь. До речі, я підозріло ставляться до людей, які кажуть "я ніколи не зрозумію ...", коли вони легко зможуть ... :-)
Андріс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.