IP 주소 저장-varchar (45) vs varbinary (16)


11

- 나는 두 개의 필드와 테이블을 만들려고 IDBIGINTIPAddress같은 하나 varchar(45)또는 varbinary(16). 아이디어는 모든 고유 한 IP 주소를 저장하고 다른 테이블 ID의 실제 참조 대신 참조 를 사용하는 것 IP address입니다.

일반적으로 IDfor 주어진 IP address또는 (주소를 찾을 수없는 경우) 주소를 삽입하고 생성 된을 반환 하는 저장 프로 시저를 만들 것 ID입니다.

나는 많은 레코드를 가질 것으로 예상하지만 (정확하게 몇 개인 지 알 수는 없지만) 위의 저장 프로 시저를 가능한 한 빨리 실행해야합니다. 따라서 실제 IP 주소를 텍스트 또는 바이트 형식으로 저장하는 방법이 궁금합니다. 어느 것이 더 좋을까요?

SQL CLRIP 주소 바이트를 문자열 및 그 반대로 변환하는 함수를 이미 작성 했으므로 변환은 문제가 아닙니다 ( IPv4및 모두 사용 IPv6).

검색을 최적화하기 위해 인덱스를 만들어야한다고 생각하지만 IP address필드를 클러스터형 인덱스에 포함 시키거나 별도의 인덱스를 생성해야하며 검색 속도가 빠른 유형은 무엇입니까?


2
적어도 IPv4의 경우 왜 4 개의 tinyint를 사용하지 않습니까? 그런 다음 실제로 읽을 수 있으며 변환을 수행 할 필요가 없습니다. 특정 유형의 검색 (정확한 일치, 서브넷 등)을 나타 내기 위해 모든 종류의 지속 형 계산 열을 만들 수도 있습니다.
Aaron Bertrand

이 경우에만 해당되는 경우 IPv4주소를 변환 INT하고 필드를 인덱스 키로 사용합니다. 그러나 IPv6두 개의 BIGINT필드 를 사용해야 하고 한 필드에 값을 저장하는 것을 선호합니다. 더 자연스러운 것 같습니다.
gotqn

1
아직도 4 개의 TINYINT 대신 INT를 이해하지 못합니까? 동일한 스토리지, 쉬운 디버깅, 덜 난해한 IMHO. 검증과 의미가 다른 완전히 다른 두 가지 유형이있는 경우 동일한 열을 사용해야하는 이유는 무엇입니까? 단일 컬럼이 더 단순하다고 내기한다면, 왜 SQL_VARIANT를 사용하지 않더라도 걱정할 필요가 없습니다. 당신은 날짜와 문자열과 숫자를 저장할 수 있으며 모두가 하나의 거대한 쓸모없는 열에 큰 파티를 가질 수 있습니다 ...
Aaron Bertrand

IP 주소는 어디에서 오는가? 마스크 / 서브넷 (예 : 10.10.10.1/124)이 포함됩니까? 나는 이것이 웹 서버 로그에서 오는 것을 보았고 BIGINT로 쉽게 번역되지 않는다 (물론 0이 실제로 -2.14xxxx 억이라고 가정하기 위해 정규화를 통합하지 않는 한 계산에는 부호없는 INT가 필요하므로 INT는 작동하지 않습니다). 서브넷 마스크는 추가 TINYINT 필드 일 수 있습니다. 그러나 위도 / 경도 DB와 일치시켜 맵으로 매핑하려면 BIGINT로 저장하고 싶다는 것을 이해합니다. 그러나 Aaron이 언급했듯이, 그것은 지속적인 계산 열이 될 수 있습니다.
Solomon Rutzky

답변:


13

실제 IP 주소를 텍스트 또는 바이트 형식으로 저장하는 방법 어느 것이 더 좋을까요?

"텍스트"여기를 의미하기 때문에 VARCHAR(45)와 "바이트"를 의미하지 VARBINARY(16), 내가 말할 것이다 : 어느 쪽도 아니 .

다음 정보가 제공됩니다 ( IPv6의 Wikipedia 기사에서 ).

주소 표현
IPv6 주소의 128 비트는 각각 16 비트의 8 개 그룹으로 표시됩니다. 각 그룹은 4 개의 16 진수로 작성되며 그룹은 콜론 (:)으로 구분됩니다. 주소 2001 : 0db8 : 0000 : 0000 : 0000 : ff00 : 0042 : 8329가이 표현의 예입니다.

편의상 IPv6 주소는 가능한 경우 다음 규칙을 적용하여 더 짧은 표기법으로 축약 할 수 있습니다.

  • 16 진 숫자 그룹에서 하나 이상의 선행 0이 제거됩니다. 이것은 일반적으로 선행 0을 모두 또는 전혀 수행하지 않습니다. 예를 들어 그룹 0042는 42로 변환됩니다.
  • 연속적인 제로 섹션은 이중 콜론 (: :)으로 대체됩니다. 여러 번 사용하면 주소가 결정되지 않으므로 이중 콜론은 주소에서 한 번만 사용할 수 있습니다. RFC 5952는 생략 된 단일 섹션 0을 나타 내기 위해 이중 콜론을 사용하지 말 것을 권장합니다. [41]

이 규칙을 적용한 예 :

        초기 주소 : 2001 : 0db8 : 0000 : 0000 : 0000 : ff00 : 0042 : 8329
        각 그룹에서 모든 선행 0을 제거한 후 : 2001 : db8 : 0 : 0 : 0 : ff00 : 42 : 8329
        0의 연속 섹션을 생략 한 후 : 2001 : db8 :: ff00 : 42 : 8329

먼저 8 개의 VARBINARY(2)필드를 사용하여 8 개의 그룹을 나타냅니다. 그룹 5-8의 필드는 NULLIPv6 주소에만 사용되므로 필드 여야 합니다. 그룹 1-4의 필드는 NOT NULLIPv4 및 IPv6 주소 모두에 사용되는 필드 여야 합니다.

(A 하나에 그들을 결합 반대로 각 그룹의 독립을 유지함으로써 VARCHAR(45)하거나 VARBINARY(16)또는이 개 BIGINT두 가지 이점을 얻을 필드) :

  1. 주소를 특정 표현으로 재구성하는 것이 훨씬 쉽습니다. 그렇지 않으면 연속적인 0 그룹을 (: :)으로 바꾸려면 파싱해야합니다. 그것들을 별개로 유지하면 이것을 용이하게하기 위해 간단한 IF/ IIF/ CASE문이 허용됩니다.
  2. 당신도 가능하게하여 IPv6 주소 공간의 톤을 절약 할 수 ROW COMPRESSION또는 PAGE COMPRESSION. 두 가지 유형의 압축은 0x000 바이트를 차지하는 필드를 허용하므로 , 이러한 0 그룹은 모두 비용이 들지 않습니다. 반면에 위의 예제 주소를 위키피디아 따옴표로 저장 한 경우 중간에있는 모든 0의 3 개 세트는 전체 공간을 차지합니다 (작업을 수행하지 VARCHAR(45)않고 축소 표기법을 사용 하지 않는 한). 그러나 인덱싱에는 효과적이지 않을 수 있으며 전체 형식으로 재구성하기 위해 특별한 구문 분석이 필요하므로 옵션이 아니라고 가정하십시오 .-).

네트워크를 캡처해야하는 TINYINT경우 um, [Network]:-) 라는 필드를 작성하십시오.

네트워크 값에 대한 자세한 내용 은 IPv6 주소에 대한 다른 Wikipedia 기사의 정보입니다 .

네트워크

IPv6 네트워크는 2의 거듭 제곱 크기의 인접한 IPv6 주소 그룹 인 주소 블록을 사용합니다. 주소의 선행 비트 세트는 주어진 네트워크의 모든 호스트에 대해 동일하며 네트워크의 주소 또는 라우팅 접두어 라고합니다 .

네트워크 주소 범위는 CIDR 표기법으로 작성됩니다. 네트워크는 블록의 첫 번째 주소 (모두 0으로 끝남), 슬래시 (/) 및 접두사의 비트 단위 크기와 같은 10 진수 값으로 표시됩니다. 예를 들어 2001 : db8 : 1234 :: / 48로 작성된 네트워크는 2001 : db8 : 1234 : 0000 : 0000 : 0000 : 0000 : 0000에서 시작하여 2001 : db8 : 1234 : ffff : ffff : ffff : ffff에서 끝납니다. : ffff.

인터페이스 주소의 라우팅 접두사에는 CIDR 표기법으로 주소가 직접 표시 될 수 있습니다. 예를 들어 서브넷 2001 : db8 : a :: / 64에 연결된 2001 : db8 : a :: 123 주소의 인터페이스 구성은 2001 : db8 : a :: 123/64로 작성됩니다.


인덱싱의 경우 8 그룹 필드에 비 클러스터형 인덱스를 만들고, 포함하기로 결정한 경우 네트워크 필드를 만들 수 있습니다.


최종 결과는 다음과 같아야합니다.

CREATE TABLE [IPAddress]
(
  IPAddressID INT          NOT NULL IDENTITY(-2147483648, 1),
  Group8      VARBINARY(2) NULL, -- IPv6 only, NULL for IPv4
  Group7      VARBINARY(2) NULL, -- IPv6 only, NULL for IPv4
  Group6      VARBINARY(2) NULL, -- IPv6 only, NULL for IPv4
  Group5      VARBINARY(2) NULL, -- IPv6 only, NULL for IPv4
  Group4      VARBINARY(2) NOT NULL, -- both
  Group3      VARBINARY(2) NOT NULL, -- both
  Group2      VARBINARY(2) NOT NULL, -- both
  Group1      VARBINARY(2) NOT NULL, -- both
  Network     TINYINT      NULL
);

ALTER TABLE [IPAddress]
  ADD CONSTRAINT [PK_IPAddress]
  PRIMARY KEY CLUSTERED
  (IPAddressID ASC)
  WITH (FILLFACTOR = 100, DATA_COMPRESSION = PAGE);

CREATE NONCLUSTERED INDEX [IX_IPAddress_Groups]
  ON [IPAddress] (Group1 ASC, Group2 ASC, Group3 ASC, Group4 ASC,
         Group5 ASC, Group6 ASC, Group7 ASC, Group8 ASC, Network ASC)
  WITH (FILLFACTOR = 100, DATA_COMPRESSION = PAGE);

노트:

  • BIGINTID 필드 에 사용할 계획 이지만 4,294,967,295 이상의 고유 한 값을 실제로 캡처 할 것으로 예상하십니까? 그렇다면 필드를 BIGINT로 변경하고 시드 값을 0으로 변경할 수도 있습니다. 그러나 그렇지 않으면 INT를 사용하고 최소값으로 시작하여 해당 데이터 유형의 전체 범위를 사용할 수 있습니다. .
  • 원하는 경우 하나 이상의 NONpersisted Computed Columns를이 테이블에 추가하여 IPAddress의 텍스트 표현을 리턴 할 수 있습니다.
  • Group * 필드는 테이블에서 의도적으로 8에서 1 로 내려 가서 SELECT *예상되는 순서로 필드를 리턴하도록 배열됩니다. 그러나 지수는 1에서 8 까지 올라가고 있습니다.
  • 텍스트 형식으로 값을 나타내는 계산 열의 예 (완료되지 않은)는 다음과 같습니다.

    ALTER TABLE [IPAddress]
      ADD TextAddress AS (
    IIF([Group8] IS NULL,
        -- IPv4
        CONCAT(CONVERT(TINYINT, [Group4]), '.', CONVERT(TINYINT, [Group3]), '.',
          CONVERT(TINYINT, [Group2]), '.', CONVERT(TINYINT, [Group1]),
          IIF([Network] IS NOT NULL, CONCAT('/', [Network]), '')),
        -- IPv6
        LOWER(CONCAT(
          CONVERT(VARCHAR(4), [Group8], 2), ':', CONVERT(VARCHAR(4), [Group7], 2), ':',
          CONVERT(VARCHAR(4), [Group6], 2), ':', CONVERT(VARCHAR(4), [Group5], 2), ':',
          CONVERT(VARCHAR(4), [Group4], 2), ':', CONVERT(VARCHAR(4), [Group3], 2), ':',
          CONVERT(VARCHAR(4), [Group2], 2), ':', CONVERT(VARCHAR(4), [Group1], 2),
          IIF([Network] IS NOT NULL, CONCAT('/', [Network]), '')
         ))
       ) -- end of IIF
    );

    테스트:

    INSERT INTO IPAddress VALUES (127, 0, 0, 0, 4, 22, 222, 63, NULL); -- IPv6
    INSERT INTO IPAddress VALUES (27, 10, 1234, 0, 45673, 200, 1, 6363, 48); -- IPv6
    INSERT INTO IPAddress VALUES (NULL, NULL, NULL, NULL, 192, 168, 2, 63, NULL); -- v4
    INSERT INTO IPAddress VALUES (NULL, NULL, NULL, NULL, 192, 168, 137, 29, 16); -- v4
    
    SELECT [IPAddressID], [Group8], [Group1], [Network], [TextAddress]
    FROM IPAddress ORDER BY [IPAddressID];

    결과:

    IPAddressID   Group8   Group1   Network  TextAddress
    -----------   ------   ------   -------  ---------------------
    -2147483646   0x007F   0x003F   NULL     007f:0000:0000:0000:0004:0016:00de:003f
    -2147483645   0x001B   0x18DB   48       001b:000a:04d2:0000:b269:00c8:0001:18db/48
    -2147483644   NULL     0x003F   NULL     192.168.2.63
    -2147483643   NULL     0x001D   16       192.168.137.29/16

SQL 서버 2005로 열을 정의하는 것 VARDECIMAL이상 VARBINARY부터 DATA_COMPRESSION사용할 수 없습니다?
Matt

@SolomonRutzky 자세한 설명 감사합니다. 궁금합니다. 주소 범위를 어떻게 검색합니까? 예를 들어 시작 및 종료 IP 주소 형식으로 IP 위치 정보 데이터를 제공하는 데이터 공급 업체가 있습니다. 주어진 IP가 속하는 범위를 찾아야합니다.
J Weezy

@JWeezy 천만에요 :). 시작 및 끝 IP 주소는 어떻게 저장됩니까? IPv4 또는 v6 주소를 사용하고 있습니까?
Solomon Rutzky

@SolomonRutzky 둘 다. IPv4는 정수로 저장할 수 있기 때문에 문제가되지 않습니다. 불행히도 SQL Server에는 128 비트 정수 또는 숫자 관련 데이터 형식이 없으므로 처리 할 수 ​​없습니다. 따라서 IPv6의 경우 VARBINARY (16)에 저장 한 다음 BETWEEN 연산자를 사용하여 범위를 검색합니다. 그러나 IP 범위에서 여러 결과를 얻었습니다. 정확하지 않다고 생각합니다. 가능한 경우 IPv4와 IPv6 모두에 대해 동일한 데이터 유형을 사용하고 싶습니다.
J Weezy

@JWeezy 나는 BINARY(16);-) 을 제안하려고했다 . 시작 / 종료 범위와 적어도 두 개의 행을 반환하는 예를 하나 주시겠습니까? 하나는 유효하고 하나는 유효하지 않습니다. VARbinary가 일부 값을 단축시킬 수 있습니다.
Solomon Rutzky

1

작을수록 항상 빠릅니다. 값이 작을수록 단일 페이지에 더 많은 값을 넣을 수 있으므로 IO가 적고 B 트리가 더 얕을 수 있습니다.

물론 다른 모든 것 (번역 오버 헤드, 가독성, 호환성, CPU로드, 인덱스 Sargability 등)은 동일합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.