メインコンテンツへスキップ

MySQLでIPv4/IPv6アドレスをVARBINARY(16)型として効率的に保存・検索する具体的なSQLとPHP実装

    MySQLにおいてIPv4およびIPv6アドレスを効率的に格納・検索するには、VARBINARY(16) 型を採用し、MySQLの標準関数である INET6_ATON()(文字列からバイナリへ変換)と INET6_NTOA()(バイナリから文字列へ変換)を組み合わせるのが最適です。

    文字列型(VARCHAR(45))と比較してデータサイズを大幅に削減(IPv4は4バイト、IPv6は16バイト)でき、インデックス効率と検索速度が向上します。

    1. テーブル設計(DDL)

    SQL

    CREATE TABLE access_logs (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        user_id INT UNSIGNED NULL,
        ip_address VARBINARY(16) NOT NULL,
        created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        INDEX idx_ip_address (ip_address)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
    

    2. 基本的なSQLクエリの考え方

    • 登録 (INSERT):

      1. SQL

    -- 文字列IPをバイナリに変換して保存(IPv4/IPv6自動判別)
    INSERT INTO access_logs (ip_address) VALUES (INET6_ATON('192.0.2.1'));
    INSERT INTO access_logs (ip_address) VALUES (INET6_ATON('2001:db8::1'));
    
    • 取得 (SELECT):

    SQL

    -- バイナリから人間が読める文字列形式に復元して取得
    SELECT id, INET6_NTOA(ip_address) AS ip_string, created_at 
    FROM access_logs;
    
    • 検索 (WHERE):

    SQL

    -- インデックスを効かせるため、カラム側ではなく検索値側に INET6_ATON を適用する
    SELECT * FROM access_logs 
    WHERE ip_address = INET6_ATON('192.0.2.1');
    

    3. PHPによる実装例(PDOを使用)

    バイナリ変換を「MySQL側(SQL関数)」で行う方法と、「PHP側(inet_pton)」で行う方法の2通りがありますが、SQL側で INET6_ATON() / INET6_NTOA() を使う方がPHPのバイナリバインドの扱いが容易で安全です。

    登録処理(INSERT)

    PHP

    function logAccess(PDO $pdo, string $clientIp, ?int $userId = null): bool
    {
        // 1. IPアドレス形式の妥当性検証 (IPv4 / IPv6 両対応)
        if (!filter_var($clientIp, FILTER_VALIDATE_IP)) {
            return false;
        }
    
        // 2. INET6_ATON() を通して保存
        $sql = "INSERT INTO access_logs (user_id, ip_address, created_at) 
                VALUES (:user_id, INET6_ATON(:ip), NOW())";
    
        $stmt = $pdo->prepare($sql);
        return $stmt->execute([
            ':user_id' => $userId,
            ':ip'      => $clientIp,
        ]);
    }
    

    単一検索処理(SELECT by IP)

    PHP

    function getLogsByIp(PDO $pdo, string $searchIp): array
    {
        if (!filter_var($searchIp, FILTER_VALIDATE_IP)) {
            return [];
        }
    
        // カラム側に式を書かず、条件値に INET6_ATON() を当てることでインデックスを使用
        $sql = "SELECT id, user_id, INET6_NTOA(ip_address) AS ip_address, created_at 
                FROM access_logs 
                WHERE ip_address = INET6_ATON(:ip)";
    
        $stmt = $pdo->prepare($sql);
        $stmt->execute([':ip' => $searchIp]);
    
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
    

    4. CIDR範囲検索(サブネット検索)の応用

    INET6_ATON() で生成されるバイナリデータは、ビット単位の比較演算が可能です。特定のサブネット(例: 192.0.2.0/24)に含まれるレコードを抽出する場合は、以下のようにビットマスク(&)を用いて判定できます。

    SQL

    -- IPv4: /24 (255.255.255.0) の範囲検索例
    SELECT id, INET6_NTOA(ip_address) AS ip 
    FROM access_logs 
    WHERE ip_address >= INET6_ATON('192.0.2.0') 
      AND ip_address <= INET6_ATON('192.0.2.255');
    

    ※ 範囲検索(BETWEEN や >= / <=)で記述すれば、B-treeインデックス(INDEX idx_ip_address)をフル活用して高速にスキャンされます。

    あなたへのおすすめ