在單腳本中連接多個 MySQL 數據庫時,mysql_query() 若不顯式指定連接標識符,會默認使用「最後打開的連接」,導致 SQL 執行錯誤或操作錯誤的庫。本文通過經典報錯案例,深度剖析隱式連接機制,提供兩種正確解法並對比優劣。
<?php
$conn1 = mysql_connect("127.0.0.1", "root", "root", "db1");
mysql_select_db("db1", $conn1);
$conn2 = mysql_connect("127.0.0.1", "root", "root", "db2");
mysql_select_db("db2", $conn2);
// 此處未指定連接,默認使用 $conn2(最後打開的)
$sql = "select * from ip";
$query = mysql_query($sql); // 實際在 db2 執行,但 ip 表在 db1 → 報錯
if ($row = mysql_fetch_array($query)) echo $row[0] . "\n";
報錯:PHP Warning: mysql_fetch_array() expects parameter 1 to be resource, boolean given —— 因 mysql_query 返回 false(查詢失敗)。
mysql_query(string $query, resource $link_identifier = null):
$link_identifier 省略 → 使用「最近一次成功打開的連接」(即最後一個 mysql_connect 返回的資源)。$conn2 最後打開,故所有無參數 mysql_query 均在 db2 上執行。<?php
$conn1 = mysql_connect("127.0.0.1", "root", "root", true); // 第 4 參數 true 強制新連接
mysql_select_db("db1", $conn1);
$conn2 = mysql_connect("127.0.0.1", "root", "root", true);
mysql_select_db("db2", $conn2);
$sql1 = "select * from ip";
$query1 = mysql_query($sql1, $conn1); // 顯式指定 conn1
if ($row = mysql_fetch_array($query1)) echo $row[0] . "\n";
$sql2 = "select * from web";
$query2 = mysql_query($sql2, $conn2); // 顯式指定 conn2
if ($row = mysql_fetch_array($query2)) echo $row[0];
優點:意圖明確,不依賴連接打開順序,代碼可讀性強。
注意:mysql_connect 第 4 參數 $new_link = true 確保不復用現有連接(PHP 4.2.0+ 支持)。
<?php
$conn1 = mysql_connect("127.0.0.1", "root", "root");
mysql_select_db("db1", $conn1);
$conn2 = mysql_connect("127.0.0.1", "root", "root");
mysql_select_db("db2", $conn2);
// 在表名前加數據庫名,無需指定連接標識符
$sql1 = "select * from db1.ip";
$query1 = mysql_query($sql1); // 自動使用最近連接(conn2),但 SQL 明確指向 db1
if ($row = mysql_fetch_array($query1)) echo $row[0] . "\n";
$sql2 = "select * from db2.web";
$query2 = mysql_query($sql2);
if ($row = mysql_fetch_array($query2)) echo $row[0];
優點:代碼簡潔,適合「同一 MySQL 實例下不同庫」的場景。
缺點:耦合數據庫名稱,跨實例遷移時需修改 SQL;若用戶權限僅限特定庫,可能無權跨庫查詢。
<?php
// 配置數組
$dbs = [
'db1' => ['dsn' => 'mysql:host=127.0.0.1;dbname=db1;charset=utf8mb4', 'user' => 'root', 'pass' => 'root'],
'db2' => ['dsn' => 'mysql:host=127.0.0.1;dbname=db2;charset=utf8mb4', 'user' => 'root', 'pass' => 'root'],
];
// 建立連接池
$pdoPool = [];
foreach ($dbs as $name => $cfg) {
$pdoPool[$name] = new PDO($cfg['dsn'], $cfg['user'], $cfg['pass'], [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]);
}
// 使用:顯式從池獲取連接
$stmt = $pdoPool['db1']->query("SELECT * FROM ip");
$row = $stmt->fetch();
echo $row['id'] ?? 'N/A';
$stmt = $pdoPool['db2']->query("SELECT * FROM web");
$row = $stmt->fetch();
echo $row['id'] ?? 'N/A';
PDO 物件本身即連接標識,天然避免隱式連接歧義,且支持預處理、異常模式、事務等企業級特性。
| 方案 | 適用場景 | 優點 | 缺點 |
|---|---|---|---|
顯式 mysql_query($sql, $link) | 維護舊項目 | 最小改動,邏輯清晰 | 依賴棄用擴展,不安全 |
SQL 加庫前綴 db.table | 同實例多庫、舊項目 | 無需管理多連接變量 | 耦合库名,權限受限,跨實例失效 |
| PDO 連接池 | 所有新項目 | 現代標準,功能全,無隱式陷阱 | 需重構舊代碼 |
「隱式默認」是許多經典 Bug 溫床。多連接場景下,**永遠顯式傳遞連接對象**,或遷移至 PDO/MySQLi 享受物件化連接管理。若仍在使用 mysql_*,請制定遷移計劃,儘早擁抱現代數據庫交互方式。