はじめに
PHPでMySQLデータベースを扱う際、適切な接続管理は非常に重要です。データベース接続を開いた後は、適切にクローズしてリソースを解放する必要があります。そのための重要な関数がmysql_close
関数でした。
重要な注意点:この関数はPHP 5.5.0で非推奨となり、PHP 7.0.0で完全に削除されました。 しかし、データベース接続の適切な管理方法を理解することは、現在のWeb開発においても極めて重要な概念です。
mysql_close関数とは?
mysql_close
関数は、指定されたMySQLデータベース接続を閉じるPHP関数でした。この関数を使用することで、不要になった接続リソースを明示的に解放し、メモリリークやコネクション不足を防ぐことができていました。
基本的な構文
bool mysql_close([resource $link_identifier])
$link_identifier
(オプション): 閉じるMySQLコネクション識別子- 戻り値: 成功時はtrue、失敗時はfalse
基本的な使用例
単純な接続とクローズ
// データベース接続(古い方式)
$connection = mysql_connect('localhost', 'username', 'password');
if (!$connection) {
die('接続エラー: ' . mysql_error());
}
mysql_select_db('database_name', $connection);
// データベース操作を実行
$query = "SELECT * FROM users LIMIT 10";
$result = mysql_query($query, $connection);
if ($result) {
while ($row = mysql_fetch_array($result)) {
echo $row['name'] . "<br>";
}
mysql_free_result($result); // 結果セットの解放
}
// 接続を明示的にクローズ
if (mysql_close($connection)) {
echo "データベース接続を正常に閉じました。";
} else {
echo "接続のクローズに失敗しました: " . mysql_error();
}
複数接続の管理
// 複数のデータベース接続を管理
$main_db = mysql_connect('localhost', 'main_user', 'main_pass');
$log_db = mysql_connect('logserver', 'log_user', 'log_pass');
if (!$main_db || !$log_db) {
die('データベース接続エラー');
}
mysql_select_db('main_database', $main_db);
mysql_select_db('log_database', $log_db);
try {
// メインDBでの操作
$user_query = "SELECT * FROM users WHERE status = 'active'";
$user_result = mysql_query($user_query, $main_db);
// ログDBへの記録
$log_query = "INSERT INTO access_logs (timestamp, action) VALUES (NOW(), 'user_list_accessed')";
mysql_query($log_query, $log_db);
// 処理結果の表示
if ($user_result) {
echo "アクティブユーザー一覧:<br>";
while ($user = mysql_fetch_array($user_result)) {
echo "- " . $user['name'] . "<br>";
}
mysql_free_result($user_result);
}
} finally {
// 両方の接続を適切に閉じる
if (mysql_close($main_db)) {
echo "メインDB接続を閉じました。<br>";
}
if (mysql_close($log_db)) {
echo "ログDB接続を閉じました。<br>";
}
}
接続管理の重要性とベストプラクティス
リソース管理の基本原則
class DatabaseManager {
private $connections = [];
public function connect($host, $user, $pass, $db_name, $connection_name = 'default') {
$connection = mysql_connect($host, $user, $pass);
if (!$connection) {
throw new Exception("接続エラー: " . mysql_error());
}
if (!mysql_select_db($db_name, $connection)) {
mysql_close($connection);
throw new Exception("データベース選択エラー: " . mysql_error());
}
$this->connections[$connection_name] = $connection;
echo "データベース接続 '{$connection_name}' を確立しました。\n";
return $connection;
}
public function getConnection($connection_name = 'default') {
if (!isset($this->connections[$connection_name])) {
throw new Exception("接続 '{$connection_name}' が見つかりません。");
}
return $this->connections[$connection_name];
}
public function closeConnection($connection_name = 'default') {
if (isset($this->connections[$connection_name])) {
$connection = $this->connections[$connection_name];
if (mysql_close($connection)) {
echo "接続 '{$connection_name}' を正常に閉じました。\n";
unset($this->connections[$connection_name]);
return true;
} else {
echo "接続 '{$connection_name}' のクローズに失敗しました。\n";
return false;
}
}
echo "接続 '{$connection_name}' は既に閉じられているか、存在しません。\n";
return false;
}
public function closeAllConnections() {
$closed_count = 0;
foreach ($this->connections as $name => $connection) {
if ($this->closeConnection($name)) {
$closed_count++;
}
}
echo "合計 {$closed_count}個の接続を閉じました。\n";
return $closed_count;
}
// デストラクタで確実に全接続を閉じる
public function __destruct() {
if (!empty($this->connections)) {
echo "デストラクタによる接続クリーンアップを実行中...\n";
$this->closeAllConnections();
}
}
}
// 使用例
$dbManager = new DatabaseManager();
try {
// 複数のデータベースに接続
$dbManager->connect('localhost', 'user1', 'pass1', 'main_db', 'main');
$dbManager->connect('localhost', 'user2', 'pass2', 'log_db', 'logs');
// データベース操作を実行
$main_conn = $dbManager->getConnection('main');
$log_conn = $dbManager->getConnection('logs');
$result = mysql_query("SELECT COUNT(*) as user_count FROM users", $main_conn);
if ($result) {
$row = mysql_fetch_array($result);
echo "総ユーザー数: " . $row['user_count'] . "\n";
mysql_free_result($result);
// ログに記録
$log_query = "INSERT INTO system_logs (message, timestamp) VALUES ('User count checked', NOW())";
mysql_query($log_query, $log_conn);
}
} catch (Exception $e) {
echo "エラー: " . $e->getMessage() . "\n";
} finally {
// 明示的に全接続を閉じる
$dbManager->closeAllConnections();
}
長時間実行スクリプトでの接続管理
function processLargeDataset() {
$connection = mysql_connect('localhost', 'username', 'password');
mysql_select_db('large_database', $connection);
$batch_size = 1000;
$offset = 0;
$processed_count = 0;
echo "大量データの処理を開始します...\n";
while (true) {
// バッチごとにデータを取得
$query = "SELECT * FROM large_table LIMIT $batch_size OFFSET $offset";
$result = mysql_query($query, $connection);
if (!$result) {
echo "クエリエラー: " . mysql_error() . "\n";
break;
}
$row_count = mysql_num_rows($result);
if ($row_count === 0) {
echo "全データの処理が完了しました。\n";
break;
}
// データを処理
while ($row = mysql_fetch_array($result)) {
// 何らかの処理を実行
processRecord($row);
$processed_count++;
// 進捗表示
if ($processed_count % 5000 === 0) {
echo "処理済み: {$processed_count}件\n";
}
}
mysql_free_result($result);
$offset += $batch_size;
// メモリ使用量をチェック
$memory_usage = memory_get_usage(true) / 1024 / 1024; // MB
echo "現在のメモリ使用量: " . number_format($memory_usage, 2) . "MB\n";
// メモリ使用量が多い場合は接続を一度閉じて再接続
if ($memory_usage > 100) {
echo "メモリ使用量が多いため、接続をリセットします。\n";
mysql_close($connection);
// ガベージコレクションを強制実行
gc_collect_cycles();
// 再接続
$connection = mysql_connect('localhost', 'username', 'password');
mysql_select_db('large_database', $connection);
$memory_usage_after = memory_get_usage(true) / 1024 / 1024;
echo "リセット後のメモリ使用量: " . number_format($memory_usage_after, 2) . "MB\n";
}
// 少し休む(サーバー負荷軽減)
usleep(100000); // 0.1秒
}
// 最終的な接続クローズ
if (mysql_close($connection)) {
echo "データベース接続を正常に閉じました。\n";
}
echo "総処理件数: {$processed_count}件\n";
}
function processRecord($record) {
// レコード処理のロジック
// 実際の処理内容に応じて実装
}
エラーハンドリングと接続状態の確認
接続状態の監視
function monitorDatabaseConnection($connection) {
// 接続が有効かチェック
if (!is_resource($connection)) {
echo "警告: 接続リソースが無効です。\n";
return false;
}
if (get_resource_type($connection) !== 'mysql link') {
echo "警告: 無効なMySQLリンクリソースです。\n";
return false;
}
// 簡単な接続テストクエリを実行
$test_result = mysql_query("SELECT 1", $connection);
if ($test_result) {
mysql_free_result($test_result);
echo "接続は正常です。\n";
return true;
} else {
echo "接続テストに失敗しました: " . mysql_error($connection) . "\n";
return false;
}
}
function safeCloseConnection($connection, $connection_name = '不明') {
echo "接続 '{$connection_name}' のクローズを試行中...\n";
// 接続の状態を確認
if (!monitorDatabaseConnection($connection)) {
echo "接続は既に無効な状態です。\n";
return false;
}
// 実行中のクエリがないか確認(可能な場合)
// 注意: 実際のmysql拡張では実行中クエリの確認は制限的
// 接続を閉じる
$start_time = microtime(true);
$close_result = mysql_close($connection);
$close_time = (microtime(true) - $start_time) * 1000; // ミリ秒
if ($close_result) {
echo "接続 '{$connection_name}' を {$close_time}ms で正常に閉じました。\n";
return true;
} else {
echo "接続 '{$connection_name}' のクローズに失敗しました: " . mysql_error() . "\n";
return false;
}
}
// 使用例
$connection = mysql_connect('localhost', 'username', 'password');
if ($connection) {
mysql_select_db('test_database', $connection);
// 何らかのデータベース操作
$result = mysql_query("SELECT * FROM users LIMIT 5", $connection);
if ($result) {
echo "クエリが成功しました。\n";
mysql_free_result($result);
}
// 接続を安全に閉じる
safeCloseConnection($connection, 'メインDB接続');
} else {
echo "データベース接続に失敗しました。\n";
}
例外処理との組み合わせ
class DatabaseConnection {
private $connection;
private $connection_info;
public function __construct($host, $user, $password, $database) {
$this->connection_info = [
'host' => $host,
'user' => $user,
'database' => $database
];
try {
$this->connect();
} catch (Exception $e) {
throw new Exception("データベース接続の初期化に失敗しました: " . $e->getMessage());
}
}
private function connect() {
$this->connection = mysql_connect(
$this->connection_info['host'],
$this->connection_info['user'],
// パスワードは実際の実装では安全に管理
'password'
);
if (!$this->connection) {
throw new Exception("MySQL接続エラー: " . mysql_error());
}
if (!mysql_select_db($this->connection_info['database'], $this->connection)) {
// 接続は成功したが、DB選択に失敗した場合は接続を閉じる
mysql_close($this->connection);
throw new Exception("データベース選択エラー: " . mysql_error());
}
echo "データベースに正常に接続しました。\n";
}
public function query($sql) {
if (!$this->isConnected()) {
throw new Exception("データベース接続が確立されていません。");
}
$result = mysql_query($sql, $this->connection);
if ($result === false) {
throw new Exception("クエリエラー: " . mysql_error($this->connection) . " - SQL: " . $sql);
}
return $result;
}
public function isConnected() {
return is_resource($this->connection) && get_resource_type($this->connection) === 'mysql link';
}
public function close() {
if ($this->isConnected()) {
$close_result = mysql_close($this->connection);
if ($close_result) {
echo "データベース接続を正常に閉じました。\n";
$this->connection = null;
return true;
} else {
echo "データベース接続のクローズに失敗しました。\n";
return false;
}
} else {
echo "接続は既に閉じられているか、無効です。\n";
return true;
}
}
// デストラクタで確実に接続を閉じる
public function __destruct() {
if ($this->isConnected()) {
echo "デストラクタによる接続クリーンアップを実行中...\n";
$this->close();
}
}
}
// 使用例
try {
$db = new DatabaseConnection('localhost', 'username', 'test_database');
// データベース操作
$result = $db->query("SELECT * FROM users WHERE status = 'active'");
if ($result) {
echo "アクティブユーザー一覧:\n";
while ($row = mysql_fetch_array($result)) {
echo "- " . $row['name'] . "\n";
}
mysql_free_result($result);
}
// 明示的に接続を閉じる
$db->close();
} catch (Exception $e) {
echo "エラーが発生しました: " . $e->getMessage() . "\n";
}
よくある問題とトラブルシューティング
問題1: 接続リークの検出と防止
function detectConnectionLeaks() {
// スクリプト実行前後でのリソース使用量を比較
$initial_memory = memory_get_usage();
$connections = [];
echo "接続リークテストを開始します...\n";
echo "初期メモリ使用量: " . number_format($initial_memory / 1024, 2) . " KB\n";
// 意図的に接続を作成(リークのシミュレーション)
for ($i = 0; $i < 10; $i++) {
$conn = mysql_connect('localhost', 'username', 'password');
if ($conn) {
$connections[] = $conn;
echo "接続 #{$i} を作成しました。\n";
// 一部の接続を意図的に閉じない(リークのシミュレーション)
if ($i % 3 === 0) {
mysql_close($conn);
echo "接続 #{$i} を閉じました。\n";
$connections[$i] = null;
}
}
$current_memory = memory_get_usage();
echo "現在のメモリ使用量: " . number_format($current_memory / 1024, 2) . " KB\n";
}
echo "\n=== リーク検出結果 ===\n";
$leaked_connections = 0;
foreach ($connections as $index => $conn) {
if ($conn !== null && is_resource($conn)) {
echo "リーク検出: 接続 #{$index} が開いたままです。\n";
$leaked_connections++;
// リークした接続を修復
if (mysql_close($conn)) {
echo "リークした接続 #{$index} を修復しました。\n";
}
}
}
$final_memory = memory_get_usage();
$memory_diff = $final_memory - $initial_memory;
echo "\n=== メモリ使用量レポート ===\n";
echo "初期メモリ: " . number_format($initial_memory / 1024, 2) . " KB\n";
echo "最終メモリ: " . number_format($final_memory / 1024, 2) . " KB\n";
echo "差分: " . number_format($memory_diff / 1024, 2) . " KB\n";
echo "リークした接続数: {$leaked_connections}\n";
}
// テスト実行
detectConnectionLeaks();
問題2: 長時間接続とタイムアウト対策
function handleLongRunningConnection() {
$connection = mysql_connect('localhost', 'username', 'password');
mysql_select_db('database', $connection);
$start_time = time();
$max_execution_time = 300; // 5分
$last_activity_time = $start_time;
echo "長時間実行処理を開始します(最大{$max_execution_time}秒)\n";
while ((time() - $start_time) < $max_execution_time) {
// 30秒ごとに接続をテスト
if ((time() - $last_activity_time) > 30) {
echo "接続テストを実行中...\n";
$test_result = mysql_query("SELECT 1", $connection);
if ($test_result) {
mysql_free_result($test_result);
echo "接続は正常です。\n";
$last_activity_time = time();
} else {
echo "接続が切断されました。再接続を試行中...\n";
// 古い接続を閉じる(エラーが発生する可能性があるため@を使用)
@mysql_close($connection);
// 再接続
$connection = mysql_connect('localhost', 'username', 'password');
if ($connection) {
mysql_select_db('database', $connection);
echo "再接続に成功しました。\n";
$last_activity_time = time();
} else {
echo "再接続に失敗しました。処理を終了します。\n";
break;
}
}
}
// 実際の処理をここに記述
// 例: バックグラウンドタスクの実行
echo "処理中... " . date('H:i:s') . "\n";
sleep(5); // 5秒間隔で処理
}
// 最終的な接続クローズ
if (is_resource($connection)) {
if (mysql_close($connection)) {
echo "長時間接続を正常に終了しました。\n";
} else {
echo "接続の終了でエラーが発生しました。\n";
}
}
$total_time = time() - $start_time;
echo "総実行時間: {$total_time}秒\n";
}
現代的な代替手段
PHP 7.0以降では、以下の方法で接続管理を行います。
1. MySQLi拡張を使用
// MySQLi接続(手続き型)
$connection = mysqli_connect('localhost', 'username', 'password', 'database');
if (!$connection) {
die('接続エラー: ' . mysqli_connect_error());
}
// データベース操作
$result = mysqli_query($connection, "SELECT * FROM users LIMIT 5");
if ($result) {
while ($row = mysqli_fetch_array($result)) {
echo $row['name'] . "<br>";
}
mysqli_free_result($result);
}
// 接続を閉じる
if (mysqli_close($connection)) {
echo "接続を正常に閉じました。";
} else {
echo "接続のクローズに失敗しました: " . mysqli_error($connection);
}
// オブジェクト指向形式
$mysqli = new mysqli('localhost', 'username', 'password', 'database');
if ($mysqli->connect_error) {
die('接続エラー: ' . $mysqli->connect_error);
}
// データベース操作
$result = $mysqli->query("SELECT * FROM users LIMIT 5");
if ($result) {
while ($row = $result->fetch_assoc()) {
echo $row['name'] . "<br>";
}
$result->free();
}
// 接続を閉じる
if ($mysqli->close()) {
echo "接続を正常に閉じました。";
}
2. PDOを使用(推奨)
try {
// PDO接続
$pdo = new PDO('mysql:host=localhost;dbname=database;charset=utf8mb4',
'username', 'password', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]);
// データベース操作
$stmt = $pdo->prepare("SELECT * FROM users WHERE status = ?");
$stmt->execute(['active']);
while ($row = $stmt->fetch()) {
echo $row['name'] . "<br>";
}
// PDOでは明示的なクローズは通常不要(変数のスコープ終了時に自動的に閉じられる)
// しかし、明示的に閉じたい場合は以下のようにする
$pdo = null;
echo "PDO接続を閉じました。";
} catch (PDOException $e) {
echo "エラー: " . $e->getMessage();
}
3. 現代的な接続管理クラス
class ModernDatabaseManager {
private $pdo;
private $config;
public function __construct($config) {
$this->config = $config;
$this->connect();
}
private function connect() {
try {
$dsn = "mysql:host={$this->config['host']};dbname={$this->config['database']};charset=utf8mb4";
$this->pdo = new PDO($dsn, $this->config['username'], $this->config['password'], [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_PERSISTENT => false // 永続接続を無効化
]);
echo "データベースに接続しました。\n";
} catch (PDOException $e) {
throw new Exception("データベース接続エラー: " . $e->getMessage());
}
}
public function query($sql, $params = []) {
try {
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
return $stmt;
} catch (PDOException $e) {
throw new Exception("クエリエラー: " . $e->getMessage());
}
}
public function isConnected() {
try {
return $this->pdo && $this->pdo->query('SELECT 1')->fetchColumn() === 1;
} catch (PDOException $e) {
return false;
}
}
public function close() {
if ($this->pdo) {
$this->pdo = null;
echo "データベース接続を閉じました。\n";
return true;
}
return false;
}
public function __destruct() {
$this->close();
}
}
// 使用例
$config = [
'host' => 'localhost',
'username' => 'username',
'password' => 'password',
'database' => 'database'
];
try {
$db = new ModernDatabaseManager($config);
// データベース操作
$stmt = $db->query("SELECT * FROM users WHERE status = ?", ['active']);
echo "アクティブユーザー一覧:\n";
while ($row = $stmt->fetch()) {
echo "- " . $row['name'] . "\n";
}
// 接続状態確認
if ($db->isConnected()) {
echo "データベース接続は正常です。\n";
}
// 明示的にクローズ
$db->close();
} catch (Exception $e) {
echo "エラー: " . $e->getMessage() . "\n";
}
レガシーコード移行戦略
ステップ1: 現状の把握
# mysql_close使用箇所の検索
grep -r "mysql_close" /path/to/your/project
# 関連する接続管理コードの検索
grep -r "mysql_connect\|mysql_pconnect" /path/to/your/project
ステップ2: 移行用ヘルパー関数
// 互換性レイヤー
function closeDatabase($connection) {
// PDOの場合
if ($connection instanceof PDO) {
$connection = null;
return true;
}
// MySQLiの場合
if ($connection instanceof mysqli) {
return $connection->close();
}
// 手続き型MySQLiの場合
if (is_resource($connection) && get_resource_type($connection) === 'mysql link') {
if (function_exists('mysqli_close')) {
return mysqli_close($connection);
}
}
// レガシー関数(互換性のため)
if (function_exists('mysql_close')) {
return mysql_close($connection);
}
return false;
}
ステップ3: 段階的移行
// Phase 1: 古いコード
$connection = mysql_connect('localhost', 'user', 'pass');
// ... データベース操作 ...
mysql_close($connection);
// Phase 2: 互換性レイヤー使用
$connection = mysql_connect('localhost', 'user', 'pass');
// ... データベース