PDO本身就是为多数据库兼容设计的,无需自行封装抽象层;DSN需严格按数据库类型构造,SQL方言差异须手动处理,连接池与错误重试等需额外实现。

用 PDO 写多数据库兼容代码最实际的起点
直接上结论:不用自己封装抽象层,PDO 本身就是为这事设计的。它不绑定具体数据库,只要驱动装了、DSN 写对,mysql、pgsql、sqlite 甚至 sqlsrv 都能共用同一套 prepare/execute 流程。
DSN 构造必须按数据库类型严格区分
不同数据库的连接字符串(DSN)格式差异大,写错一个字符就报 PDOException: could not find driver 或 invalid data source name。不是语法问题,是协议层不认。
-
MySQL:mysql:host=localhost;dbname=test;charset=utf8mb4 -
PostgreSQL:pgsql:host=localhost;dbname=test;user=pguser;password=pgpass -
SQLite:sqlite:/path/to/db.sqlite(注意是绝对路径,相对路径容易找不到) -
SQL Server:sqlsrv:server=localhost;Database=test(Windows + SQLSRV 扩展,Linux 上得用pdo_odbc)
别试图用变量拼接 DSN——比如把 host 和 port 拆开再组合,PDO 不解析键值对,只认完整字符串格式。
跨库时要主动处理 SQL 方言差异
PDO 负责连接和参数绑定,但不翻译 SQL。比如分页、默认值、自动递增字段的写法,各库完全不同:
立即学习“PHP免费学习笔记(深入)”;
- MySQL 用
LIMIT 10 OFFSET 20,PostgreSQL 同样支持,但 SQLite 3.25+ 才支持OFFSET;老版本得用LIMIT 20,10 - 插入后取 ID:
lastInsertId()在 MySQL/SQLite 下直接可用,在 PostgreSQL 必须显式指定序列名,如lastInsertId('users_id_seq') - 布尔字段:MySQL 存
TINYINT(1),PostgreSQL 用BOOLEAN,PHP 绑定true时,PDO 不会自动转类型,得靠PDO::PARAM_BOOL显式声明
这意味着“通用 SQL”只存在于简单 CRUD 场景;一旦涉及函数(NOW() vs CURRENT_TIMESTAMP)、类型转换或锁机制,就得分支处理。
连接池、长连接和错误重试得自己补足
PDO 默认不支持连接池,Persistent 连接(PDO::ATTR_PERSISTENT => true)在 Apache 的 prefork 模式下反而容易导致连接泄漏。生产环境如果换库,这些底层行为不会自动适配:
- MySQL 断连后
PDO::ATTR_ERRMODE设为PDO::ERRMODE_EXCEPTION才能捕获HY000类错误,但 PostgreSQL 可能抛08006(连接失败)或25P02(事务中止),需分别判断 - 重连逻辑不能写在
__construct里——构造失败就实例化不出,得包一层工厂函数,失败时重新 new - SQLite 是文件锁,高并发下
database is locked错误得用PDO::ATTR_TIMEOUT控制等待,而不是靠重试
真正麻烦的从来不是“怎么连”,而是“连上了之后,怎么让同一段业务逻辑在不同数据库上行为一致”。方言适配、事务边界、锁粒度、甚至时区处理,都得在 PDO 之上再叠一层薄抽象——但这一层,必须从具体需求出发,不能为了“通用”而提前设计。











