PDO для Microsoft SQL Server: подключение, запросы и оптимальные практики

Раздел: PHP -> Работа с базами данных в PHP

Основы PDO и SQL Server

Расширение PDO (PHP Data Objects) предоставляет универсальный интерфейс для работы с различными базами данных. Для подключения к Microsoft SQL Server используется драйвер pdo_sqlsrv (или альтернативный pdo_dblib через FreeTDS). Данная статья рассматривает наиболее эффективное решение на основе официального драйвера Microsoft.

Основное решение: подключение с использованием PDO SQLSRV

Для стабильной работы рекомендуется использовать драйвер pdo_sqlsrv, устанавливаемый через PECL или менеджер расширений. Строка подключения включает имя сервера, имя базы данных и параметры аутентификации.

$dsn = 'sqlsrv:Server=localhost;Database=testdb';
$user = 'sa';
$password = 'secret';
try {
$pdo = new PDO($dsn, $user, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
echo 'Ошибка подключения: ' . $e->getMessage();
}

Php pdo sqlsrv (pdo для sql server)

После подключения выполняются подготовленные запросы с параметрами, что защищает от SQL-инъекций и повышает производительность при повторных вызовах.

$stmt = $pdo->prepare('SELECT * FROM users WHERE id = ?');
$stmt->execute([1]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);

Php artisan db (команда artisan db в laravel)

Типичная ошибка: “could not find driver” означает, что расширение pdo_sqlsrv не установлено. Решение: установить через pecl install pdo_sqlsrv или включить в php.ini.

Проблема кодировки: SQL Server по умолчанию использует Latin1. Для работы с UTF-8 нужно указать параметры в DSN: CharacterSet=UTF-8 или настроить кодировку на стороне запроса.

Вариант 1: Как настроить аутентификацию Windows?

Для входа через текущего пользователя Windows указывается параметр Trusted_Connection=Yes.

$dsn = 'sqlsrv:Server=.;Database=mydb;Trusted_Connection=Yes';
$pdo = new PDO($dsn); // без имени и пароля

Php pdo pgsql (pdo для postgresql)

Ошибка “Login failed for user” возникает, если у пользователя Windows нет прав доступа к SQL Server. Команда создание логина: CREATE LOGIN [DOMAIN\User] FROM WINDOWS;

Вариант 2: Как выполнять простые запросы без параметров?

Метод query() подходит для однократных статических запросов, но не рекомендуется для данных из пользовательского ввода.

$rows = $pdo->query('SELECT * FROM products')->fetchAll(PDO::FETCH_OBJ);

Create table php (создание таблицы в php)

Использование query() с динамическими значениями ведёт к SQL-инъекциям. Предпочтительнее подготовленные запросы.

Вариант 3: Как обрабатывать ошибки разными режимами?

PDO поддерживает три режима: ERRMODE_SILENT (по умолчанию), ERRMODE_WARNING и ERRMODE_EXCEPTION. Последний рекомендуется для надёжной обработки.

$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
try {
$pdo->query('SELECT invalid');
} catch (PDOException $e) {} // не сработает, только warning
// Лучше:
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

Select from users php (sql запрос select from users в php)

При ERRMODE_SILENT ошибки незаметны. Обязательно проверять верное количество затронутых строк после UPDATE через rowCount().

Вариант 4: Как использовать именованные параметры в подготовленных запросах?

Именованные плейсхолдеры улучшают читаемость кода.

$stmt = $pdo->prepare('UPDATE users SET name = :name WHERE id = :id');
$stmt->execute([':name' => 'Alice', ':id' => 10]);

Table user php (таблица user в php)

Нельзя смешивать именованные и позиционные параметры в одном запросе.

Вариант 5: Как выполнять транзакции и откаты?

Транзакции гарантируют атомарность изменений.

$pdo->beginTransaction();
try {
$pdo->exec('INSERT INTO logs (message) VALUES ("start")');
$pdo->exec('UPDATE accounts SET balance = balance - 100 WHERE id = 1');
$pdo->commit();
} catch (PDOException $e) {
$pdo->rollBack();
echo 'Транзакция откачена: ' . $e->getMessage();
}

Admin db php (администрирование базы данных в php)

Вложенные транзакции не поддерживаются PDO, но можно использовать сохранения (savepoints) на уровне SQL Server. Ошибка при commit внутри другой транзакции приведёт к исключению.

Вариант 6: Как получить ID последней вставленной записи?

Метод lastInsertId() работает для столбцов с типом IDENTITY или GENERATED ALWAYS.

$pdo->exec('INSERT INTO users (name) VALUES ("Bob")');
$id = $pdo->lastInsertId(); // возвращает последний автоинкремент

Php db query (выполнение запроса к базе данных в php)

Если таблица не имеет IDENTITY, lastInsertId() вернёт 0. Для SQL Server также можно использовать OUTPUT INSERTED.id.

Вариант 7: Как вызывать хранимые процедуры с выходными параметрами?

Для OUTPUT параметров нужно использовать bindParam() с указанием длины.

$stmt = $pdo->prepare('{CALL get_user_count(?)}');
$count = 0;
$stmt->bindParam(1, $count, PDO::PARAM_INT, 10);
$stmt->execute();
echo $count;

Php pdo query (выполнение запросов pdo)

Порядок параметров в процедуре должен совпадать с позиционными. Неверно заданный тип при bindParam() может привести к ошибке.

Вариант 8: Как работать с большими бинарными данными (BLOB)?

Для вставки изображений или файлов используется потоки PDO::PARAM_LOB.

$stmt = $pdo->prepare('INSERT INTO files (data) VALUES (?)');
$handle = fopen('image.png', 'rb');
$stmt->bindParam(1, $handle, PDO::PARAM_LOB);
$stmt->execute();
fclose($handle);

Php mysqli fetch (функция mysqli_fetch в php)

При чтении BLOB через fetch() нужно указать PDO::FETCH_OBJ и обращаться к свойству как к строке, или использовать stream_get_contents().

Вариант 9: Как защититься от SQL-инъекций при построении динамических запросов?

Кроме подготовленных запросов, можно использовать quote() для экранирования строк, но это менее предпочтительно.

$safeValue = $pdo->quote($_GET['name']);
$pdo->exec('SELECT * FROM users WHERE name = ' . $safeValue);
// quote() добавляет кавычки и экранирует спецсимволы

Db error php (ошибка базы данных в php)

quote() не защищает от всех типов атак и не рекомендуется для чисел. Лучше использовать prepared statements.

Вариант 10: Как настроить время ожидания и другие опции соединения?

В DSN можно передавать дополнительные параметры, например, LoginTimeout=5 (сек) или ReturnDatesAsStrings=true.

$dsn = 'sqlsrv:Server=mssql:1433;Database=test;LoginTimeout=10;ReturnDatesAsStrings=TRUE';
Некоторые опции могут не поддерживаться старой версией драйвера. Рекомендуется обновлять pdo_sqlsrv до последней версии.
- Index php pdo (index.php с pdo)

Расширенные примеры работы с PDO SQLSRV

Пример 1: Подключение с несколькими опциями и обработка ошибок

Пример
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::SQLSRV_ATTR_DIRECT_QUERY => false, // использование подготовленных запросов по умолчанию
PDO::SQLSRV_ATTR_QUERY_TIMEOUT => 30, // таймаут запроса в секундах
];
$dsn = 'sqlsrv:Server=mssql.example.com,1433;Database=testdb;CharacterSet=UTF-8;TrustServerCertificate=False';
$pdo = new PDO($dsn, 'user', 'pass', $options);
echo 'Подключение установлено';
Подключение установлено

Пример 2: Подготовленный запрос с несколькими наборами результатов (MARS)

Для использования нескольких активных наборов результатов нужно включить MARS в DSN и явно переключаться между курсорами.

Пример
$dsn = 'sqlsrv:Server=.;Database=test;MultipleActiveResultSets=True';
$pdo = new PDO($dsn, 'sa', 'pass', [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$stmt1 = $pdo->query('SELECT 1 AS val');
$stmt2 = $pdo->query('SELECT 2 AS val');
while ($row1 = $stmt1->fetch()) {
while ($row2 = $stmt2->fetch()) {
echo $row1['val'] . ' - ' . $row2['val'] . '<br>';
}
// $stmt2->execute(); // можно повторно использовать после перемещения курсора
}
1 - 2

Пример 3: Работа с хранимой процедурой, возвращающей результат и выходные параметры

Пример
$proc = 'CREATE OR ALTER PROCEDURE sp_get_user
@id INT,
@name NVARCHAR(100) OUTPUT
AS
BEGIN
SELECT @name = name FROM users WHERE id = @id;
RETURN @id;
END';
$pdo->exec($proc);
$stmt = $pdo->prepare('{CALL sp_get_user(?, ?)}');
$userId = 5;
$userName = '';
$stmt->bindParam(1, $userId, PDO::PARAM_INT);
$stmt->bindParam(2, $userName, PDO::PARAM_STR, 100);
$stmt->execute();
echo 'Имя: ' . $userName;
// Получение возвращаемого значен из процедуры через fetch()
$result = $stmt->fetch(PDO::FETCH_ASSOC);
echo 'Возвращённый ID: ' . $result['RETURN_VALUE']; // при RETURN
Имя: Alice
Возвращённый ID: 5

Пример 4: Массовая вставка с использованием подготовленных запросов в цикле

Пример
$pdo->beginTransaction();
$stmt = $pdo->prepare('INSERT INTO orders (product_id, quantity) VALUES (?, ?)');
$data = [[1, 10], [2, 5], [3, 7]];
foreach ($data as $row) {
$stmt->execute($row);
}
$pdo->commit();
echo 'Вставлено ' . count($data) . ' записей';
Вставлено 3 записей

Пример 5: Работа с потоками для вставки/извлечения больших объектов

Пример
$fileContent = file_get_contents('largefile.pdf');
$stmt = $pdo->prepare('INSERT INTO documents (name, content) VALUES (?, CONVERT(VARBINARY(MAX), ?))');
$stmt->execute(['document.pdf', $fileContent]);
// Извлечение с потоком
$stmt = $pdo->query('SELECT content FROM documents WHERE id = 1');
$stmt->bindColumn(1, $blob, PDO::PARAM_LOB);
$stmt->fetch();
// $blob теперь содержит поток, можно сохранить
file_put_contents('output.pdf', stream_get_contents($blob));
Файл сохранён.

Пример 6: Использование PDO::errorInfo для диагностики ошибки

Пример
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
$stmt = $pdo->prepare('SELECT * FROM nonexistent');
$stmt->execute();
if ($stmt->errorCode() != '00000') {
$info = $stmt->errorInfo();
echo 'Код: ' . $info[0] . ', SQLSTATE: ' . $info[1] . ', Сообщение: ' . $info[2];
}
Код: 42S02, SQLSTATE: 208, Сообщение: [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Invalid object name 'nonexistent'.

Пример 7: Асинхронное выполнение с использованием PDO::SQLSRV_ATTR_DIRECT_QUERY

В некоторых сценариях (например, выполнение множества запросов без подготовки) можно отключить предварительную подготовку для повышения производительности.

Пример
$pdo->setAttribute(PDO::SQLSRV_ATTR_DIRECT_QUERY, true);
$pdo->exec('UPDATE stats SET counter = counter + 1');
$pdo->exec('UPDATE logs SET ts = GETDATE()');
// Внимание: такой режим не защищает от инъекций, если есть подставляемые значения.
Выполнено.

Пример 8: Использование PDO::FETCH_CLASS для маппинга на объект

Пример
class User {
public $id;
public $name;
}
$stmt = $pdo->query('SELECT id, name FROM users');
$users = $stmt->fetchAll(PDO::FETCH_CLASS, 'User');
foreach ($users as $user) {
echo $user->id . ' ' . $user->name . '<br>';
}
1 Alice
2 Bob

Пример 9: Изменение кодировки соединения через запрос

Если DSN не поддерживает CharacterSet, можно выполнить запрос после подключения.

Пример
$pdo->exec('SET NAMES "UTF-8"'); // эквивалент для некоторых версий SQLSRV драйвера не всегда работает; лучше указывать в DSN.

Например, для драйвера ODBC задать кодировку можно через set_charset – отсутствует. Рекомендуется использовать параметр CharacterSet=UTF-8 в строке DSN.

Пример
$dsn = 'odbc:Driver={ODBC Driver 17 for SQL Server};Server=.;Database=test;CharSet=UTF-8';
Подключение с UTF-8 установлено.

Пример 10: Комбинирование fetchAll с разными стилями

Пример
$stmt = $pdo->prepare('SELECT * FROM categories');
$stmt->execute();
$unique = $stmt->fetchAll(PDO::FETCH_COLUMN, 0); // только первый столбец
print_r($unique);
$keyValue = $stmt->fetchAll(PDO::FETCH_KEY_PAIR); // если два столбца: ключ-значение
print_r($keyValue);
Array ( [0] => 1 [1] => 2 )
Array ( [1] => Electronics [2] => Clothing )

PDO для SQL Server - comments

En
Php pdo sqlsrv (php)