应使用 information_schema.tables 查询判断表是否存在,而非 pdo::query() 执行 select 或 show tables;需预处理绑定参数、注意大小写、校验输入来源,并在高并发场景下缓存结果。

PHP 中用 PDO::query() 检查表是否存在会报错
直接执行 SELECT * FROM non_existent_table 并靠异常捕获来判断,看似简单,但实际踩坑率极高。PDO 默认不抛出异常(PDO::ATTR_ERRMODE 是 PDO::ERRMODE_SILENT),你可能根本收不到错误,还误以为表存在;即使开了 PDO::ERRMODE_EXCEPTION,这种“先查再用”的方式也属于典型防御式编程反模式——它把业务逻辑和元数据探测混在一起,容易掩盖真实问题。
- 真正该用的是数据库元数据查询,不是模拟业务查询
- MySQL 推荐走
INFORMATION_SCHEMA.TABLES,它稳定、标准、不依赖表结构或权限细节 - 别用
SHOW TABLES LIKE 'xxx':它在某些 PDO 驱动下返回结果格式不一致,且 LIKE 匹配受 SQL_MODE 影响(比如大小写敏感)
MySQL 下用 INFORMATION_SCHEMA.TABLES 安全判断
这是最可靠的方式,不触发任何表级权限检查(只要用户有对 INFORMATION_SCHEMA 的 SELECT 权限即可),也不依赖存储引擎行为。
- SQL 示例:
SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? LIMIT 1 - 参数必须用预处理绑定,防止库名/表名含特殊字符或注入风险
-
TABLE_SCHEMA对应数据库名(不是用户名),别错填成CURRENT_DATABASE()或硬编码 - 注意区分大小写:MySQL 在 Linux 下库名/表名默认区分大小写,
TABLE_NAME值需与实际建表时完全一致
Laravel 的 Schema::hasTable() 底层其实也是查 INFORMATION_SCHEMA
如果你在 Laravel 项目里用 Schema::hasTable('users'),它最终发的还是类似上面那条 SQL —— 不是靠 try/catch 执行 DESCRIBE users。这点很多人误解,以为框架做了魔法,其实只是封装了安全查询。
- 它默认使用当前连接的数据库名,不会自动 fallback 到配置里的
DB_DATABASE,如果手动切换过 DB(如DB::connection('other')->getDatabaseName()),得自己传参 - 在 SQLite 下行为不同:它查的是
sqlite_master表,所以跨数据库迁移时要注意逻辑一致性 - 不要在高并发初始化逻辑中频繁调用它——虽然单次很快,但反复查元数据仍会成为瓶颈,建议缓存结果(例如用
static $cache = [])
用 mysqli 原生扩展时别漏掉 mysqli_real_escape_string() 的陷阱
有人图快,拼接 SQL 字符串后用 mysqli_query() 执行,再用 mysqli_num_rows() 判断。这很危险:哪怕你记得过滤表名,mysqli_real_escape_string() 对数据库名无效(它只对字符串字面量起作用),而库名若来自用户输入,就可能被绕过。
立即学习“PHP免费学习笔记(深入)”;
- 正确做法:用预处理语句,或严格白名单校验库名/表名(如正则
/^[a-zA-Z_][a-zA-Z0-9_]*$/) - 更稳妥的是,把库名固定在配置里,只允许动态传入表名,并确保表名不带点(
.)、反引号(`)或空格 - 错误示例:
"SELECT 1 FROM `{$db}`.`{$table}`"—— 反引号不能防注入,只能防关键字冲突;真正的防护在参数化或白名单
实际写的时候,最常被忽略的是数据库名和表名的来源是否可信、大小写是否匹配、以及是否在多租户场景下混淆了 schema 上下文。这些点不显眼,但一出问题就是“本地好好的,上线就找不到表”。











