pdo dsn格式因数据库而异:mysql用mysql:host=;dbname=;charset=,postgresql用pgsql:host=;dbname=;user=;password=,sqlite用sqlite:/path/,sql server用sqlsrv:server=;database=;占位符须统一用?或:name(注意sql server不支持数字开头的命名参数);sql方言、事务与错误模式需手动适配并显式设置。

不同数据库的 PDO DSN 写法差异
PDO 本身不保证 SQL 语法跨库兼容,但连接层(DSN)必须按驱动要求写对,否则 PDO::__construct() 直接抛出 PDOException。MySQL、PostgreSQL、SQLite 的 DSN 格式互不通用。
- MySQL:
mysql:host=localhost;dbname=test;charset=utf8mb4(注意charset是可选参数,但强烈建议显式指定) - PostgreSQL:
pgsql:host=localhost;dbname=test;user=pguser;password=pgpass(charset不生效,需在连接后执行SET client_encoding TO 'UTF8') - SQLite:
sqlite:/path/to/db.sqlite(路径必须绝对或相对于当前工作目录,相对路径易出错) - SQL Server(pdo_sqlsrv):
sqlsrv:server=localhost;Database=test(不能用host,必须用server)
预处理语句中占位符不能混用
PDO 支持两种占位符:命名参数(:name)和问号(?),但同一语句里不能混用,且不同驱动对命名参数的支持程度不一——PostgreSQL 和 SQLite 完全支持,MySQL 5.6+ 支持,但旧版 MySQL 驱动(如 mysql,已废弃)根本不识别 :。
- 安全做法:统一用命名参数,但需确认目标环境 PDO 驱动版本(
php -i | grep "pdo_mysql version") - 更稳妥写法:全部用
?占位符,靠execute()数组顺序传参,兼容性最高 - 特别注意:SQL Server(pdo_sqlsrv)不支持命名参数中的连字符或数字开头,
:user_id可以,:2nd_param会报Invalid parameter number
SQL 方言差异必须手动适配
PDO 不做 SQL 翻译。比如分页、字符串拼接、类型转换等语法,各数据库完全不同:
- 分页:
LIMIT 10 OFFSET 20(MySQL/PostgreSQL/SQLite) vsOFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY(SQL Server 2012+) - 字符串拼接:
CONCAT(a, b)(MySQL) vsa || b(PostgreSQL/SQLite) vsa + b(SQL Server) - 获取自增 ID:
lastInsertId()在 MySQL/SQLite 返回值正常;PostgreSQL 需配合RETURNING id子句;SQL Server 要用SELECT SCOPE_IDENTITY()
实际项目中,建议把方言敏感操作封装进 DAO 方法,按 $pdo->getAttribute(PDO::ATTR_DRIVER_NAME) 动态分支,而不是硬写 SQL。
立即学习“PHP免费学习笔记(深入)”;
事务与错误模式设置要显式声明
默认情况下,PDO 的 PDO::ATTR_ERRMODE 是 PDO::ERRMODE_SILENT,出错不报异常,容易掩盖问题;而不同驱动对自动提交(autocommit)的行为也略有差异,比如 PostgreSQL 在事务中执行 DDL(如 CREATE TABLE)会直接中断事务,MySQL 则允许。
- 务必在初始化后设置:
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) - 显式控制事务:
$pdo->beginTransaction()/$pdo->commit()/$pdo->rollback(),不要依赖隐式行为 - 避免在事务中混用 DDL 和 DML,尤其在 PostgreSQL 和 SQL Server 上,DDL 会隐式提交当前事务
跨库兼容最难的不是连接或查询,而是事务边界和错误恢复逻辑——这里最容易漏掉回滚,导致数据不一致。










