VARCHAR
に点線のクワッド表記でIPアドレスを保存する ドット付きクワッドは、データベースのインデックス作成に適さない32ビットの符号なし整数の人間に優しい表現であるため、これらを格納するための最適な方法ではありません。ただし、基本的に便利な場合もあり、小規模では、クエリにテーブルスキャンが必要であるという事実は通常問題にはなりません。
MySQL Stored Functionsは、クエリで参照できる単純な関数の背後にある比較的複雑なロジックをカプセル化するための優れた方法であり、クエリを理解しやすくし、コピー/貼り付けエラーを減らす可能性があります。
それで、これが私が書いたfind_ip4_in_cidr4()
というストアド関数です。 。組み込み関数FIND_IN_SET()
-値を指定して「セット」(CIDR仕様)を指定すると、値がセットに含まれているかどうかを示す値が返されます。
まず、動作中の関数の図:
アドレスがブロック内にある場合は、プレフィックス長を返します。なぜプレフィックス長を返すのですか?ゼロ以外の整数は「true」であるため、1
を返すだけで済みます。 、ただし、一致する結果を並べ替えて、一致する複数のプレフィックスの最短または最長を検索する場合は、ORDER BY
を使用できます。 関数の戻り値。
mysql> SELECT find_ip4_in_cidr4('203.0.113.123','203.0.113.0/24');
+-----------------------------------------------------+
| find_ip4_in_cidr4('203.0.113.123','203.0.113.0/24') |
+-----------------------------------------------------+
| 24 |
+-----------------------------------------------------+
1 row in set (0.00 sec)
mysql> SELECT find_ip4_in_cidr4('192.168.100.1','192.168.0.0/16');
+-----------------------------------------------------+
| find_ip4_in_cidr4('192.168.100.1','192.168.0.0/16') |
+-----------------------------------------------------+
| 16 |
+-----------------------------------------------------+
1 row in set (0.00 sec)
ブロックにありませんか? 0(false)を返します。
mysql> SELECT find_ip4_in_cidr4('192.168.100.1','203.0.113.0/24');
+-----------------------------------------------------+
| find_ip4_in_cidr4('192.168.100.1','203.0.113.0/24') |
+-----------------------------------------------------+
| 0 |
+-----------------------------------------------------+
1 row in set (0.00 sec)
mysql> SELECT find_ip4_in_cidr4('192.168.100.1','192.168.0.0/24');
+-----------------------------------------------------+
| find_ip4_in_cidr4('192.168.100.1','192.168.0.0/24') |
+-----------------------------------------------------+
| 0 |
+-----------------------------------------------------+
1 row in set (0.00 sec)
すべてゼロのアドレスには特殊なケースがあり、-1を返します(「true」のままですが、ソート順は保持されます):
mysql> SELECT find_ip4_in_cidr4('192.168.100.1','0.0.0.0/0');
+------------------------------------------------+
| find_ip4_in_cidr4('192.168.100.1','0.0.0.0/0') |
+------------------------------------------------+
| -1 |
+------------------------------------------------+
1 row in set (0.00 sec)
ナンセンス引数はnullを返します:
mysql> SELECT find_ip4_in_cidr4('234.467.891.0','192.168.0.0/24');
+-----------------------------------------------------+
| find_ip4_in_cidr4('234.467.891.0','192.168.0.0/24') |
+-----------------------------------------------------+
| NULL |
+-----------------------------------------------------+
1 row in set (0.00 sec)
さて、codez:
DELIMITER $$
DROP FUNCTION IF EXISTS `find_ip4_in_cidr4` $$
CREATE DEFINER=`mezzell`@`%` FUNCTION `find_ip4_in_cidr4`(
_address VARCHAR(15),
_block VARCHAR(18)
) RETURNS TINYINT
DETERMINISTIC /* for a given input, this function always returns the same output */
CONTAINS SQL /* the function does not read from or write to tables */
BEGIN
-- given an IPv4 address and a cidr spec,
-- return -1 for a valid address inside 0.0.0.0/0
-- return prefix length if the address is within the block,
-- return 0 if the address is outside the block,
-- otherwise return null
DECLARE _ip_aton INT UNSIGNED DEFAULT INET_ATON(_address);
DECLARE _cidr_aton INT UNSIGNED DEFAULT INET_ATON(SUBSTRING_INDEX(_block,'/',1));
DECLARE _prefix TINYINT UNSIGNED DEFAULT SUBSTRING_INDEX(_block,'/',-1);
DECLARE _bitmask INT UNSIGNED DEFAULT (0xFFFFFFFF << (32 - _prefix)) & 0xFFFFFFFF;
RETURN CASE /* the first match, not "best" match is used in a CASE expression */
WHEN _ip_aton IS NULL OR _cidr_aton IS NULL OR /* sanity checks */
_prefix IS NULL OR _bitmask IS NULL OR
_prefix NOT BETWEEN 0 AND 32 OR
(_prefix = 0 AND _cidr_aton != 0) THEN NULL
WHEN _cidr_aton = 0 AND _bitmask = 0 THEN -1
WHEN _ip_aton & _bitmask = _cidr_aton & _bitmask THEN _prefix /* here's the only actual test needed */
ELSE 0 END;
END $$
DELIMITER ;
保存された関数に固有ではなく、ほとんどのRDBMSプラットフォームのほとんどの関数に当てはまる問題は、列がWHERE
の関数の引数として使用される場合です。 、サーバーは、インデックスを使用してクエリを最適化する関数を「後方に見る」ことはできません。