このアプローチにはスケーラビリティの問題がいくつかありますが(たとえば、都市固有のgeoipデータに移動することを選択した場合)、特定のサイズのデータに対して、かなりの最適化が提供されます。
あなたが直面している問題は、MySQLが範囲ベースのクエリをうまく最適化していないという事実です。理想的には、「より大きい」ではなく、インデックスに対して正確な( "=")ルックアップを実行する必要があるため、利用可能なデータからそのようなインデックスを作成する必要があります。このようにして、MySQLは、一致するものを探すときに評価する行がはるかに少なくなります。
これを行うには、IPアドレスの最初のオクテット(1.2.3.4から=1)に基づいてジオロケーションテーブルにインデックスを付けるルックアップテーブルを作成することをお勧めします。アイデアは、実行する必要のあるルックアップごとに、探しているIPと同じオクテットで始まらないすべてのジオロケーションIPを無視できるということです。
CREATE TABLE `ip_geolocation_lookup` (
`first_octet` int(10) unsigned NOT NULL DEFAULT '0',
`ip_numeric_start` int(10) unsigned NOT NULL DEFAULT '0',
`ip_numeric_end` int(10) unsigned NOT NULL DEFAULT '0',
KEY `first_octet` (`first_octet`,`ip_numeric_start`,`ip_numeric_end`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
次に、ジオロケーションテーブルで利用可能なデータを取得し、すべてをカバーするデータを生成する必要があります。 (最初の)ジオロケーション行がカバーするオクテット:ip_start = '5.3.0.0'
のエントリがある場合 およびip_end = '8.16.0.0'
、ルックアップテーブルにはオクテット5、6、7、および8の行が必要です。したがって...
ip_geolocation
|ip_start |ip_end |ip_numeric_start|ip_numeric_end|
|72.255.119.248 |74.3.127.255 |1224701944 |1241743359 |
変換する必要があります:
ip_geolocation_lookup
|first_octet|ip_numeric_start|ip_numeric_end|
|72 |1224701944 |1241743359 |
|73 |1224701944 |1241743359 |
|74 |1224701944 |1241743359 |
ここで誰かがネイティブMySQLソリューションを要求したので、そのデータを生成するストアドプロシージャを次に示します。
DROP PROCEDURE IF EXISTS recalculate_ip_geolocation_lookup;
CREATE PROCEDURE recalculate_ip_geolocation_lookup()
BEGIN
DECLARE i INT DEFAULT 0;
DELETE FROM ip_geolocation_lookup;
WHILE i < 256 DO
INSERT INTO ip_geolocation_lookup (first_octet, ip_numeric_start, ip_numeric_end)
SELECT i, ip_numeric_start, ip_numeric_end FROM ip_geolocation WHERE
( ip_numeric_start & 0xFF000000 ) >> 24 <= i AND
( ip_numeric_end & 0xFF000000 ) >> 24 >= i;
SET i = i + 1;
END WHILE;
END;
次に、そのストアドプロシージャを呼び出して、テーブルにデータを入力する必要があります。
CALL recalculate_ip_geolocation_lookup();
この時点で、作成したばかりのプロシージャを削除できます。ルックアップテーブルを再計算する場合を除いて、このプロシージャは不要になります。
ルックアップテーブルが配置されたら、それをクエリに統合し、最初のオクテットでクエリしていることを確認するだけです。ルックアップテーブルへのクエリは、次の2つの条件を満たすことになります。
- IPアドレスの最初のオクテットに一致するすべての行を検索します
- そのサブセットの :IPアドレスと一致する範囲の行を検索します
ステップ2はデータのサブセットに対して実行されるため、データ全体に対して範囲テストを実行するよりもかなり高速です。これがこの最適化戦略の鍵です。
IPアドレスの最初のオクテットが何であるかを理解するためのさまざまな方法があります。 ( r.ip_numeric & 0xFF000000 ) >> 24
を使用しました ソースIPは数値形式であるため:
SELECT
r.*,
g.country_code
FROM
ip_geolocation g,
ip_geolocation_lookup l,
ip_random r
WHERE
l.first_octet = ( r.ip_numeric & 0xFF000000 ) >> 24 AND
l.ip_numeric_start <= r.ip_numeric AND
l.ip_numeric_end >= r.ip_numeric AND
g.ip_numeric_start = l.ip_numeric_start;
確かに、私は最終的に少し怠惰になりました。ip_geolocation
を簡単に取り除くことができます。 ip_geolocation_lookup
を作成した場合は、テーブル全体 テーブルには国のデータも含まれています。このクエリから1つのテーブルを削除すると、少し速くなると思います。
そして最後に、これらはあなたのテーブルとは異なるので、参照のためにこの応答で使用した他の2つのテーブルがあります。ただし、互換性があると確信しています。
# This table contains the original geolocation data
CREATE TABLE `ip_geolocation` (
`ip_start` varchar(16) NOT NULL DEFAULT '',
`ip_end` varchar(16) NOT NULL DEFAULT '',
`ip_numeric_start` int(10) unsigned NOT NULL DEFAULT '0',
`ip_numeric_end` int(10) unsigned NOT NULL DEFAULT '0',
`country_code` varchar(3) NOT NULL DEFAULT '',
`country_name` varchar(64) NOT NULL DEFAULT '',
PRIMARY KEY (`ip_numeric_start`),
KEY `country_code` (`country_code`),
KEY `ip_start` (`ip_start`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
# This table simply holds random IP data that can be used for testing
CREATE TABLE `ip_random` (
`ip` varchar(16) NOT NULL DEFAULT '',
`ip_numeric` int(10) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`ip`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;