[PHP]mysql_close関数の使い方完全ガイド – データベース接続管理を詳細解説

PHP

はじめに

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');
// ... データベース

タイトルとURLをコピーしました