腳本代碼:
<?php try { $dbh = new PDO('mysql:host=localhost;dbname=test', 'root', 'root'); } catch (PDOException $e) { exit('連接數據庫失敗1'); } finally { echo "連接成功1\n"; } try { $conn = new PDO('mysql:host=localhost;dbname=test', 'root', 'root'); } catch (PDOException $e) { exit('連接數據庫失敗2'); } finally { echo "連接成功2\n"; } try { $pdo = new PDO('mysql:host=localhost;dbname=test', 'root', 'root'); } catch (PDOException $e) { exit('連接數據庫失敗3'); } finally { echo "連接成功3\n"; } echo "保持連接中...\n"; sleep(10); echo "執行結束\n";
CLI執行:
root@78ad0df34cef:/home/www/wenda/webroot# php index.php
連接成功1
連接成功2
連接成功3
保持連接中...
執行結束
在腳本sleep過程中,查看mysql的連接信息:
mysql> show processlist; +----+------+-----------+------+---------+------+----------+------------------+ | Id | User | Host | db | Command | Time | State | Info | +----+------+-----------+------+---------+------+----------+------------------+ | 3 | root | localhost | NULL | Query | 0 | starting | show processlist | | 24 | root | localhost | test | Sleep | 6 | | NULL | | 25 | root | localhost | test | Sleep | 6 | | NULL | | 26 | root | localhost | test | Sleep | 6 | | NULL | +----+------+-----------+------+---------+------+----------+------------------+ 4 rows in set (0.00 sec)
可以看到一個腳本的執行產生了三個數據庫連接,但是如果將后面的實例化的pdo實例賦值給之前實例化的pdo實例,則新的連接會替換掉前一個連接,而不會產生新的連接。所以我們在編程過程中,應該避免多次實例化pdo,而產生不必要的數據庫性能消耗。
解決方案:
1. 封裝一個單例模式的類,該類實例化的過程就是創建pdo連接的過程。我們要建立數據庫連接時,不是手動實例化pdo,而是去獲取這個類的實例。
2. 實例化pdo類時,設定 持久連接 參數:
<?php $dbh = new PDO('mysql:host=localhost;dbname=test', $user, $pass, array( PDO::ATTR_PERSISTENT => true )); ?>
官方文檔的引用:
很多 web 應用程序通過使用到數據庫服務的持久連接獲得好處。 持久連接在腳本結束后不會被關閉,且被緩存,當另一個使用相同憑證的腳本連接請求時被重用。持久連接緩存可以避免每次腳本需要與數據庫回話時建立一個新連接的開銷,從而讓 web 應用程序更快。
- 官方所說的
腳本結束
,在fpm模式下就是指一次客戶端請求的結束
。另一個使用相同憑證的腳本
也就可以對應成另一個使用相同數據庫連接憑證的客戶端請求
。首先我們要知道,這兩次客戶端的請求是根據fpm-workers的空閑情況,被分配給某個worker去執行的,所以兩次請求被分配到同一個worker的可能性很低。接着,我們闡明下面的情景。 2. 開啟持久連接之后,數據庫連接是被緩存於fpm進程之中的。如果某個fpm-worker進程中已經緩存了持久連接,此時可能出現如下兩種情況:
- 當腳本中再次執行帶 ATTR_PERSISTENT 參數的pdo連接時,會復用之前的連接,而不會產生新的連接。
- 當腳本中再次執行不帶 ATTR_PERSISTENT 參數的pdo連接時,還會再次產生一個新的數據庫連接。