Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

Tabela GeoIP junta-se à tabela de IPs no MySQL


Essa abordagem tem alguns problemas de escalabilidade (se você optar por migrar para, digamos, dados geoip específicos da cidade), mas para o tamanho de dados fornecido, ela fornecerá uma otimização considerável.

O problema que você está enfrentando é que o MySQL não otimiza muito bem as consultas baseadas em intervalo. Idealmente, você deseja fazer uma pesquisa exata ("=") em um índice em vez de "maior que", portanto, precisaremos criar um índice como esse a partir dos dados disponíveis. Dessa forma, o MySQL terá muito menos linhas para avaliar enquanto procura uma correspondência.

Para fazer isso, sugiro que você crie uma tabela de pesquisa que indexe a tabela de geolocalização com base no primeiro octeto (=1 de 1.2.3.4) dos endereços IP. A ideia é que para cada pesquisa que você fizer, você possa ignorar todos os IPs de geolocalização que não comecem com o mesmo octeto que o IP que você está procurando.
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;

Em seguida, precisamos pegar os dados disponíveis em sua tabela de geolocalização e produzir dados que cubram todos (primeiro) octetos que a linha de geolocalização cobre:​​Se você tiver uma entrada com ip_start = '5.3.0.0' e ip_end = '8.16.0.0' , a tabela de pesquisa precisará de linhas para os octetos 5, 6, 7 e 8. Então...
ip_geolocation
|ip_start       |ip_end          |ip_numeric_start|ip_numeric_end|
|72.255.119.248 |74.3.127.255    |1224701944      |1241743359    |

Deve converter para:
ip_geolocation_lookup
|first_octet|ip_numeric_start|ip_numeric_end|
|72         |1224701944      |1241743359    |
|73         |1224701944      |1241743359    |
|74         |1224701944      |1241743359    |

Como alguém aqui solicitou uma solução MySQL nativa, aqui está um procedimento armazenado que gerará esses dados para você:
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;

E então você precisará preencher a tabela chamando esse procedimento armazenado:
CALL recalculate_ip_geolocation_lookup();

Neste ponto, você pode excluir o procedimento que acabou de criar -- ele não é mais necessário, a menos que você queira recalcular a tabela de consulta.

Depois que a tabela de consulta estiver pronta, tudo o que você precisa fazer é integrá-la às suas consultas e certificar-se de que está consultando pelo primeiro octeto. Sua consulta à tabela de consulta satisfará duas condições:
  1. Encontre todas as linhas que correspondem ao primeiro octeto do seu endereço IP
  2. Desse subconjunto :encontre a linha que tem o intervalo que corresponde ao seu endereço IP

Como a etapa dois é realizada em um subconjunto de dados, é consideravelmente mais rápido do que fazer os testes de intervalo em todos os dados. Esta é a chave para esta estratégia de otimização.

Existem várias maneiras de descobrir qual é o primeiro octeto de um endereço IP; Eu usei ( r.ip_numeric & 0xFF000000 ) >> 24 já que meus IPs de origem estão em formato numérico:
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;

Agora, admito que fiquei um pouco preguiçoso no final:você poderia facilmente se livrar de ip_geolocation table completamente se você fez o ip_geolocation_lookup tabela também contém os dados do país. Suponho que descartar uma tabela dessa consulta a tornaria um pouco mais rápida.

E, finalmente, aqui estão as outras duas tabelas que usei nesta resposta para referência, pois elas diferem das suas tabelas. Tenho certeza de que são compatíveis, no entanto.
# 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;