IPがCIDRブロック内に含まれているかどうかを判断する最も速い方法は何ですか?
現時点では、CIDRアドレスを保存するたびに、開始IPアドレスと終了IPアドレスの2つの列も作成します。開始IPアドレスと終了IPアドレスにインデックスが付けられます。アドレスが含まれているネットワークを確認したい場合は、where ip between start_ip and end_ip
望ましいとは言えないようです。
右シフトされた番号を保存でき、同様にシフトされたIPアドレス(@cidrの場合は660510)と一致する可能性があります...
select @cidr, inet_aton(substring_index(@cidr,'/',1))>>(32-substring_index(@cidr,'/',-1));
+---------------+-----------------------------------------------------------------------------+
| @cidr | inet_aton(substring_index(@cidr,'/',1))>>(32-substring_index(@cidr,'/',-1)) |
+---------------+-----------------------------------------------------------------------------+
| 10.20.30.0/24 | 660510 |
+---------------+-----------------------------------------------------------------------------+
1 row in set (0.00 sec)
set @ip:='10.20.30.40';
Query OK, 0 rows affected (0.00 sec)
select @ip, inet_aton(@ip)>>(32-substring_index(@cidr,'/',-1));
+-------------+----------------------------------------------------+
| @ip | inet_aton(@ip)>>(32-substring_index(@cidr,'/',-1)) |
+-------------+----------------------------------------------------+
| 10.20.30.40 | 660510 |
+-------------+----------------------------------------------------+
1 row in set (0.00 sec)
インデックス付きの方法でこれを利用するには、サブネットマスク(シフトするビット数)を知る必要があります。それ以外の場合は、ビットシフトを体系的に比較します(つまり、考えられるネットマスクごとに(0から24ビットに)盲目的にシフトします)。
他にも最適化するソースがありますが、 http://lite.ip2location.com/database/ip-asn にあるIP2Location™LITE IP-ASNデータベースを最適化することは、概念の証明になるでしょう。
テーブル...
CREATE TABLE `ip2loc_asn` (
`asn` bigint(20) DEFAULT NULL,
`cidr` varchar(50) DEFAULT NULL,
`start_ip` bigint(20) DEFAULT NULL,
`end_ip` bigint(20) DEFAULT NULL,
`name` varchar(250) DEFAULT NULL,
KEY `ip2locasn_startip_endip` (`start_ip`,`end_ip`),
KEY `asn` (`asn`),
KEY `cidr` (`cidr`)
) ENGINE=MyISAM; -- table is recreated monthly, MyISAM is the perfect engine
サンプルデータ...
select * from ip2loc_asn limit 10;
+-------+--------------+----------+----------+-------------------------------+
| asn | cidr | start_ip | end_ip | name |
+-------+--------------+----------+----------+-------------------------------+
| 56203 | 1.0.4.0/24 | 16778240 | 16778495 | Big Red Group |
| 56203 | 1.0.5.0/24 | 16778496 | 16778751 | Big Red Group |
| 56203 | 1.0.6.0/24 | 16778752 | 16779007 | Big Red Group |
| 38803 | 1.0.7.0/24 | 16779008 | 16779263 | Goldenit Pty ltd Australia, A |
| 18144 | 1.0.64.0/18 | 16793600 | 16809983 | Energia Communications,Inc. |
| 9737 | 1.0.128.0/17 | 16809984 | 16842751 | TOT Public Company Limited |
| 9737 | 1.0.128.0/18 | 16809984 | 16826367 | TOT Public Company Limited |
| 9737 | 1.0.128.0/19 | 16809984 | 16818175 | TOT Public Company Limited |
| 23969 | 1.0.128.0/24 | 16809984 | 16810239 | TOT Public Company Limited |
| 23969 | 1.0.129.0/24 | 16810240 | 16810495 | TOT Public Company Limited |
+-------+--------------+----------+----------+-------------------------------+
10 rows in set (0.00 sec)
ネットマスクの範囲は8〜32ビットです...
select min(substring_index(cidr,'/',-1)+0), max(substring_index(cidr,'/',-1)+0) from ip2loc_asn;
+-------------------------------------+-------------------------------------+
| min(substring_index(cidr,'/',-1)+0) | max(substring_index(cidr,'/',-1)+0) |
+-------------------------------------+-------------------------------------+
| 8 | 32 |
+-------------------------------------+-------------------------------------+
1 row in set (0.33 sec)
select * from ip2loc_asn where cidr like '%/8' limit 1;
+------+-----------+----------+----------+------------------------------+
| asn | cidr | start_ip | end_ip | name |
+------+-----------+----------+----------+------------------------------+
| 3356 | 4.0.0.0/8 | 67108864 | 83886079 | Level 3 Communications, Inc. |
+------+-----------+----------+----------+------------------------------+
1 row in set (0.00 sec)
select * from ip2loc_asn where cidr like '%/32' limit 1;
+-------+---------------+-----------+-----------+------+
| asn | cidr | start_ip | end_ip | name |
+-------+---------------+-----------+-----------+------+
| 51964 | 57.72.27.1/32 | 961026817 | 961026817 | |
+-------+---------------+-----------+-----------+------+
1 row in set (0.02 sec)
現在の実行計画...
explain select * from ip2loc_asn where inet_aton('10.20.30.40') between start_ip and end_ip;
+----+-------------+------------+-------+--------------------------+--------------------------+---------+------+-------+-----------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------+-------+--------------------------+--------------------------+---------+------+-------+-----------------------+
| 1 | SIMPLE | ip2loc_asn | range | ip2loc_asn_startip_endip | ip2loc_asn_startip_endip | 9 | NULL | 10006 | Using index condition |
+----+-------------+------------+-------+--------------------------+--------------------------+---------+------+-------+-----------------------+
1 row in set (0.00 sec)
私の不器用な試み...
mysql to3_reference> alter table ip2loc_asn add column shifted_netmask int(10) unsigned;
Query OK, 626695 rows affected (4.06 sec)
Records: 626695 Duplicates: 0 Warnings: 0
mysql to3_reference> update ip2loc_asn set shifted_netmask = start_ip>>(32-substring_index(cidr,'/',-1));
Query OK, 626695 rows affected (5.98 sec)
Rows matched: 626695 Changed: 626695 Warnings: 0
mysql to3_reference> alter table ip2loc_asn add key ip2loc_asn_shiftednetmask (shifted_netmask);
Query OK, 626695 rows affected (5.83 sec)
Records: 626695 Duplicates: 0 Warnings: 0
古い方法:
select * from ip2loc_asn where inet_aton('8.8.8.0') between start_ip and end_ip;
+-------+------------+-----------------+--------------+-----------+-----------+------------------------------+
| asn | cidr | shifted_netmask | netmask_bits | start_ip | end_ip | name |
+-------+------------+-----------------+--------------+-----------+-----------+------------------------------+
| 3356 | 8.0.0.0/9 | 16 | 9 | 134217728 | 142606335 | Level 3 Communications, Inc. |
| 3356 | 8.0.0.0/8 | 8 | 8 | 134217728 | 150994943 | Level 3 Communications, Inc. |
| 15169 | 8.8.8.0/24 | 526344 | 24 | 134744064 | 134744319 | Google Inc. |
+-------+------------+-----------------+--------------+-----------+-----------+- -----------------------------+
3 rows in set (0.00 sec)
Shifted_netmaskを使用するアプローチ(望ましくない-ネットマスクのビット数を検出するために全テーブルスキャンが発生します)...
select * from ip2loc_asn where shifted_netmask = inet_aton('8.8.8.0')>>32-netmask_bits;
+-------+------------+-----------------+--------------+-----------+-----------+------------------------------+
| asn | cidr | shifted_netmask | netmask_bits | start_ip | end_ip | name |
+-------+------------+-----------------+--------------+-----------+-----------+------------------------------+
| 3356 | 8.0.0.0/8 | 8 | 8 | 134217728 | 150994943 | Level 3 Communications, Inc. |
| 3356 | 8.0.0.0/9 | 16 | 9 | 134217728 | 142606335 | Level 3 Communications, Inc. |
| 15169 | 8.8.8.0/24 | 526344 | 24 | 134744064 | 134744319 | Google Inc. |
+-------+------------+-----------------+--------------+-----------+-----------+------------------------------+
3 rows in set (0.64 sec)
望ましいアプローチは、最後のクエリからネットマスクビットのスキャンを除いたものと同様です。
PostgreSQLの補足として、 これはcidr
およびinet
タイプですぐに使用できます です。そして、もしあなたが本当にこの仕事を一流にしたいなら、 ip4r
右シフトされた番号を保存でき、同様にシフトされたIPアドレス(@cidrの場合は660510)と一致する可能性があります...
良い考えです、これは 実際にはPostgreSQLが内部的にそれらを格納する方法 です。簡単にできる
CREATE TABLE ip2loc_asn (
asn bigint,
cidr cidr,
name text
);
CREATE INDEX ON ip2loc_asn USING Gist(cidr);
INSERT INTO ip2loc_asn(asn,cidr,name)
VALUES
( 56203, '1.0.4.0/24' , 'Big Red Group' ),
( 56203, '1.0.5.0/24' , 'Big Red Group' ),
( 56203, '1.0.6.0/24' , 'Big Red Group' ),
( 38803, '1.0.7.0/24' , 'Goldenit Pty ltd Australia, A' ),
( 18144, '1.0.64.0/18' , 'Energia Communications,Inc.' ),
( 9737, '1.0.128.0/17' , 'TOT Public Company Limited' ),
( 9737, '1.0.128.0/18' , 'TOT Public Company Limited' ),
( 9737, '1.0.128.0/19' , 'TOT Public Company Limited' ),
( 23969, '1.0.128.0/24' , 'TOT Public Company Limited' ),
( 23969, '1.0.129.0/24' , 'TOT Public Company Limited' );
network-type operator でクエリできるようになりました
test=# SELECT * FROM ip2loc_asn WHERE cidr >> '1.0.129.0';
asn | cidr | name
-------+--------------+----------------------------
9737 | 1.0.128.0/17 | TOT Public Company Limited
9737 | 1.0.128.0/18 | TOT Public Company Limited
9737 | 1.0.128.0/19 | TOT Public Company Limited
23969 | 1.0.129.0/24 | TOT Public Company Limited
これはインデックスでも発生します。
主な問題は、オプティマイザが一致する開始と終了のペアが1つあるか、ロットがあるかを認識していないことです。したがって、最適化の試みはすべて、テーブルスキャン、または少なくとも大きな範囲スキャンで止まっています。
あなたはどちらから始めなければなりませんか? IPアドレス?またはCIDRブロック?もう一方の検索を効率的に行うために、最初のデータを並べ替える必要があるかもしれないので、お願いします。
this では、すべての2 ^ 32(または同等のIPv6)IPアドレスのテーブルを作成および維持する方法を説明します。 start_ip
列のみを使用し、次の行からend_ip
を推測します。これは、割り当てられていないすべてのIP範囲がテーブルに行を持っている必要があることを意味します。 (これは大きな負担ではなく、せいぜい行数を2倍にします。)これで、実質的にすべての操作は本質的にO(1)-つまり、WHERE ip >= start_ip ORDER BY start_ip DESC LIMIT 1
のようなものです。 「すぐに」答えを取得します。テーブルスキャンも範囲スキャンもありません。「ポイントクエリ」よりも効果的です(事実上)。end_ipをテストする必要さえないことに注意してください。警告:重複範囲は処理されません。一部のアプリケーション(おそらくあなたのものではない)は、オーバーラップを必要としないように適合させることができます。
それをCIDRに適応させる方法は? 1つの方法は、CIDRのテーブルを私のバリエーションに変えることです。あなたはそれを行う方法に精通しています。主な違いは、end_ipの欠如と「所有されていない」範囲の追加です。したがって、CIDRを「始め」てIPを検索する必要がある場合、これは考えられる答えです。