mysql_list_tables() 自 PHP 4.3 起棄用,PHP 7.0 移除。現代 PHP 應用必須使用 PDO (PHP Data Objects) 搭配 INFORMATION_SCHEMA 或 SHOW TABLES,並配合預處理語句、異常處理、型別安全。本文提供三種標準實現、效能對比、遷移清單、常見坑點。
一、為什麼棄用 mysql_list_tables?
- 擴展移除:
ext/mysql PHP 7.0 移除,僅保留 mysqli、PDO_MySQL
- 無預處理:資料庫名稱直接拼接 SQL,注入風險
- 無異常機制:回傳
false 需手動檢查,易漏錯
- 無型別提示:IDE 靜態分析無法覆蓋
二、方案對比
| 方案 | SQL 來源 | 優點 | 缺點 | 推薦場景 |
| PDO + INFORMATION_SCHEMA.TABLES | 標準 SQL | 跨資料庫標準、可攜帶、支援過濾 (BASE TABLE/VIEW)、可 JOIN 統計資訊 | 權限需 SELECT ON INFORMATION_SCHEMA、MySQL 早期版本較慢 | 通用、需額外元數據 (表大小、引擎、行數) |
| PDO + SHOW TABLES | MySQL 專用 | 簡潔、MySQL 內部優化快、無額外權限 | 非標準、難以擴展過濾條件、無法直接獲取表類型 | 純 MySQL 環境、只需表名列表 |
| mysqli + SHOW TABLES | MySQL 專用 | 原生擴展、支援預處理、程序式/物件雙介面 | 綁定 mysqli、非 PDO 通用 | 舊專案 mysqli 遷移、不想引入 PDO |
三、標準實現:PDO + INFORMATION_SCHEMA (推薦)
prepare($sql);
$stmt->bindValue(':db', $database, PDO::PARAM_STR);
$stmt->bindValue(':type', $tableType, PDO::PARAM_STR);
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// 使用範例
try {
$dsn = 'mysql:host=127.0.0.1;port=3306;dbname=mydb;charset=utf8mb4';
$pdo = new PDO($dsn, 'app_user', 'strong_password', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false, // 真實預處理
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_general_ci"
]);
$tables = getTables($pdo, 'mydb');
foreach ($tables as $t) {
echo "{$t['TABLE_NAME']} ({$t['ENGINE']}) ~{$t['TABLE_ROWS']} rows\n";
}
} catch (PDOException $e) {
error_log('DB Error: ' . $e->getMessage());
throw new RuntimeException('Database unavailable', 0, $e);
}
四、輕量實現:PDO + SHOW TABLES
function getTableNames(PDO $pdo, string $database): array {
$stmt = $pdo->prepare('SHOW TABLES FROM `' . $pdo->quote($database) . '`');
// 注意:SHOW TABLES 不支援預處理佔位符,需手動 quote 識別符
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_COLUMN, 0); // 只取第一欄
}
五、進階:獲取表結構 (COLUMNS、INDEXES、FK)
function getTableSchema(PDO $pdo, string $database, string $table): array {
$cols = $pdo->prepare('
SELECT COLUMN_NAME, DATA_TYPE, COLUMN_TYPE, IS_NULLABLE, COLUMN_DEFAULT,
COLUMN_KEY, EXTRA, COLUMN_COMMENT
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = :db AND TABLE_NAME = :tbl
ORDER BY ORDINAL_POSITION
');
$cols->execute([':db' => $database, ':tbl' => $table]);
$idxs = $pdo->prepare('
SELECT INDEX_NAME, COLUMN_NAME, NON_UNIQUE, SEQ_IN_INDEX, INDEX_TYPE
FROM INFORMATION_SCHEMA.STATISTICS
WHERE TABLE_SCHEMA = :db AND TABLE_NAME = :tbl
ORDER BY INDEX_NAME, SEQ_IN_INDEX
');
$idxs->execute([':db' => $database, ':tbl' => $table]);
return [
'columns' => $cols->fetchAll(PDO::FETCH_ASSOC),
'indexes' => $idxs->fetchAll(PDO::FETCH_ASSOC)
];
}
六、效能與權限注意事項
| 項目 | 說明 |
| INFORMATION_SCHEMA 權限 | 需 GRANT SELECT ON INFORMATION_SCHEMA.* TO 'app_user'@'%'; (MySQL 8.0 預設有) |
| 大量表效能 | 超過 1000 表時 INFORMATION_SCHEMA 明顯變慢,建議緩存結果 (Redis TTL 5-10 分鐘) |
| SHOW TABLES 效能 | MySQL 內部快取,極快,但無法過濾 VIEW/BASE TABLE |
| 識別符引用 | 資料庫/表名含特殊字元時,用 `quote()` 或 `$pdo->quote()` (僅字串值),識別符需手動反引號包裹 |
七、遷移清單:從 mysql_list_tables 到 PDO
- 確認 PHP 版本 ≥ 7.1 (建議 8.1+),安裝
php-pdo、php-pdo-mysql
- 建立統一
Database::getPdo() 單例/工廠,配置 ATTR_ERRMODE=EXCEPTION
- 替換所有
mysql_list_tables($db) → getTables($pdo, $db)
- 替換
mysql_fetch_row() 迴圈 → foreach ($tables as $row)
- 移除
@ 抑制錯誤,統一 try-catch (PDOException)
- 執行靜態分析 (Psalm/PHPStan Level 5+) 檢查型別安全
- 壓測驗證效能、記憶體、連線池行為
八、Laravel / Symfony / Yii2 框架原生做法
| 框架 | API | 範例 |
| Laravel | Schema::getTableListing() | $tables = Schema::connection('mysql')->getTableListing();
| Symfony (Doctrine) | $conn->getSchemaManager()->listTables() | $tables = $em->getConnection()->getSchemaManager()->listTables();
| Yii2 | yii\db\Schema::getTableNames() | $tables = Yii::$app->db->getSchema()->getTableNames();
拋棄 mysql_list_tables 不僅是語法遷移,更是邁向型別安全、異常驅動、可測試、框架無關的現代 PHP 工程化關鍵步驟。下一篇我們將探討 Linux 系統下 MySQL 重要目錄結構、密碼管理與 systemd 服務化運維標準化,敬請期待。