Lưu trữ địa chỉ IP - varchar (45) so với varbinary (16)


11

Tôi sẽ tạo ra một bảng với hai lĩnh vực - IDnhư BIGINTIPAddresslà một trong hai varchar(45)hoặc varbinary(16). Ý tưởng là lưu trữ tất cả các địa chỉ IP duy nhất và sử dụng tham chiếu IDthay vì thực tế IP addresstrong các bảng khác.

Nói chung, tôi sẽ tạo một thủ tục được lưu trữ đang trả về đã IDcho IP addresshoặc (nếu không tìm thấy địa chỉ) chèn địa chỉ và trả lại địa chỉ đã tạo ID.

Tôi hy vọng sẽ có nhiều hồ sơ (tôi không thể biết chính xác có bao nhiêu), nhưng tôi cần thủ tục được lưu trữ ở trên để được thực thi nhanh nhất có thể. Vì vậy, tôi tự hỏi làm thế nào để lưu trữ địa chỉ IP thực tế - ở định dạng văn bản hoặc byte. Cái nào sẽ tốt hơn?

Tôi đã viết các SQL CLRhàm để chuyển đổi byte địa chỉ IP thành chuỗi và ngược lại, vì vậy chuyển đổi không phải là vấn đề (làm việc với cả hai IPv4IPv6).

Tôi đoán tôi cần tạo một chỉ mục để tối ưu hóa tìm kiếm, nhưng tôi không chắc mình nên đưa IP addresstrường vào chỉ mục được nhóm hay để tạo một chỉ mục riêng và loại tìm kiếm nào sẽ nhanh hơn?


2
Đối với IPv4 ít nhất, tại sao không phải là 4 tiny? Sau đó, chúng thực sự có thể đọc được và bạn không phải thực hiện bất kỳ chuyển đổi nào. Bạn cũng có thể tạo tất cả các loại cột được tính toán bền vững để thể hiện các loại tìm kiếm cụ thể (kết hợp chính xác, mạng con, v.v.).
Aaron Bertrand

Nếu đó là trường hợp chỉ dành cho IPv4tôi đoán tôi sẽ chuyển đổi địa chỉ thành INTvà sử dụng trường làm khóa chỉ mục. Nhưng đối với IPv6tôi cần sử dụng hai BIGINTtrường và tôi thích lưu trữ giá trị trong một trường - đối với tôi tự nhiên hơn.
gotqn

1
Vẫn không hiểu tại sao INT thay vì 4 TINYINT? Cùng lưu trữ, gỡ lỗi dễ dàng hơn, ít vô nghĩa hơn, IMHO. Nếu bạn có hai loại hoàn toàn khác nhau với xác nhận và ý nghĩa khác nhau, tại sao chúng cần sử dụng cùng một cột? Nếu bạn đang đặt cược rằng một cột đơn giản hơn, tại sao không sử dụng SQL_VariANT, thì bạn không phải lo lắng về bất cứ điều gì. Bạn có thể lưu trữ ngày và chuỗi và số và mọi người có thể có một bữa tiệc lớn trong một cột khổng lồ, vô dụng ...
Aaron Bertrand

Địa chỉ IP đến từ đâu? Họ có bao giờ bao gồm mặt nạ / mạng con (tức là 10.10.10.1/124) không? Tôi đã thấy điều này xuất hiện từ nhật ký máy chủ web và không dịch dễ dàng sang BIGINT (INT sẽ không hoạt động vì tính toán yêu cầu INT không dấu, trừ khi, tất nhiên, bạn kết hợp việc chuẩn hóa để giả sử 0 là -2,14xxxx tỷ). Tôi đoán mặt nạ mạng con có thể chỉ là một trường TINYINT bổ sung. Nhưng tôi hiểu rằng muốn lưu trữ dưới dạng BIGINT nếu muốn khớp với DB với vĩ độ / kinh độ để vạch ra chúng. Nhưng như Aaron đã đề cập, đó có thể là một col tính toán bền bỉ.
Solomon Rutzky

Câu trả lời:


12

Làm thế nào để lưu trữ địa chỉ IP thực tế - ở định dạng văn bản hoặc byte. Cái nào sẽ tốt hơn?

Vì "văn bản" ở đây đề cập đến VARCHAR(45)và "byte" đề cập đến VARBINARY(16), tôi sẽ nói: không .

Đưa ra các thông tin sau (từ bài viết Wikipedia về IPv6 ):

Biểu diễn địa chỉ
128 bit của một địa chỉ IPv6 được thể hiện trong 8 nhóm 16 bit mỗi địa chỉ. Mỗi nhóm được viết dưới dạng 4 chữ số thập lục phân và các nhóm được phân tách bằng dấu hai chấm (:). Địa chỉ 2001: 0db8: 0000: 0000: 0000: ff00: 0042: 8329 là một ví dụ về đại diện này.

Để thuận tiện, địa chỉ IPv6 có thể được viết tắt thành các ký hiệu ngắn hơn bằng cách áp dụng các quy tắc sau, nếu có thể.

  • Một hoặc nhiều số 0 đứng đầu từ bất kỳ nhóm chữ số thập lục phân nào bị xóa; điều này thường được thực hiện cho tất cả hoặc không một trong số các số 0 đứng đầu. Ví dụ, nhóm 0042 được chuyển đổi thành 42.
  • Các phần liên tiếp của số 0 được thay thế bằng dấu hai chấm (: :). Dấu hai chấm chỉ có thể được sử dụng một lần trong một địa chỉ, vì nhiều lần sử dụng sẽ khiến địa chỉ không xác định. RFC 5952 khuyến nghị không nên sử dụng dấu hai chấm để biểu thị một phần số không được bỏ qua. [41]

Một ví dụ về việc áp dụng các quy tắc này:

        Địa chỉ ban đầu: 2001: 0db8: 0000: 0000: 0000: ff00: 0042: 8329
        Sau khi xóa tất cả các số 0 đứng đầu trong mỗi nhóm: 2001: db8: 0: 0: 0: ff00: 42: 8329
        Sau khi bỏ qua các phần liên tiếp của số 0: 2001 : db8 :: ff00: 42: 8329

Tôi sẽ bắt đầu bằng cách sử dụng 8 VARBINARY(2)trường để đại diện cho 8 nhóm. Các trường cho Nhóm 5 - 8 phải là NULLvì chúng sẽ chỉ được sử dụng cho các địa chỉ IPv6. Các trường cho Nhóm 1 - 4 phải là NOT NULLvì chúng sẽ được sử dụng cho cả địa chỉ IPv4 và IPv6.

Bằng cách giữ cho mỗi nhóm độc lập (trái ngược với việc kết hợp chúng thành một VARCHAR(45)hoặc một VARBINARY(16)hoặc thậm chí hai BIGINTlĩnh vực), bạn sẽ có được hai lợi ích chính:

  1. Việc tái cấu trúc địa chỉ thành bất kỳ đại diện cụ thể nào sẽ dễ dàng hơn nhiều. Mặt khác, để thay thế các nhóm số 0 liên tiếp bằng (: :) bạn sẽ phải phân tích cú pháp. Giữ chúng riêng biệt cho phép các câu lệnh / IF/ đơn giản để tạo điều kiện thuận lợi cho việc này.IIFCASE
  2. Bạn sẽ tiết kiệm được một tấn dung lượng trên các địa chỉ IPv6 bằng cách bật ROW COMPRESSIONhoặc PAGE COMPRESSION. Vì cả hai loại COMPRESSION sẽ cho phép các trường 0x00chiếm 0 byte, nên tất cả các nhóm số 0 đó sẽ không làm bạn mất bất cứ chi phí nào. Mặt khác, nếu bạn lưu trữ địa chỉ ví dụ từ phía trên (trong trích dẫn Wikipedia), thì 3 bộ số không ở giữa sẽ chiếm hết dung lượng của chúng (trừ khi bạn đang thực hiện VARCHAR(45)và đi với ký hiệu rút gọn , nhưng điều đó có thể không hoạt động tốt để lập chỉ mục và sẽ yêu cầu phân tích cú pháp đặc biệt để tái cấu trúc nó thành định dạng đầy đủ, vì vậy hãy giả sử rằng đó không phải là một tùy chọn ;-).

NẾU bạn cần chụp Mạng, tạo một TINYINTtrường cho cái gọi là, ừm, [Network]:-)

Để biết thêm thông tin về giá trị Mạng, đây là một số thông tin từ một bài viết Wikipedia khác về địa chỉ IPv6 :

Mạng

Mạng IPv6 sử dụng một khối địa chỉ là một nhóm các địa chỉ IPv6 liền kề có kích thước có sức mạnh bằng hai. Tập hợp các bit hàng đầu của các địa chỉ giống hệt nhau cho tất cả các máy chủ trong một mạng nhất định và được gọi là địa chỉ hoặc tiền tố định tuyến của mạng .

Phạm vi địa chỉ mạng được viết bằng ký hiệu CIDR. Một mạng được biểu thị bằng địa chỉ đầu tiên trong khối (kết thúc bằng tất cả các số 0), dấu gạch chéo (/) và giá trị thập phân bằng với kích thước tính theo bit của tiền tố. Ví dụ: mạng được viết là 2001: db8: 1234 :: / 48 bắt đầu tại địa chỉ 2001: db8: 1234: 0000: 0000: 0000: 0000: 0000 và kết thúc vào năm 2001: db8: 1234: ffff: ffff: ffff: ffff : ffff.

Tiền tố định tuyến của một địa chỉ giao diện có thể được biểu thị trực tiếp bằng địa chỉ bằng ký hiệu CIDR. Ví dụ: cấu hình của giao diện có địa chỉ 2001: db8: a :: 123 được kết nối với mạng con 2001: db8: a :: / 64 được viết là 2001: db8: a :: 123/64.


Để lập chỉ mục, tôi sẽ nói tạo một chỉ mục Không phân cụm trên các trường Nhóm 8 và có thể là trường Mạng nếu bạn quyết định đưa vào đó.


Kết quả cuối cùng sẽ giống như sau:

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);

Ghi chú:

  • Tôi nhận ra rằng bạn có kế hoạch sử dụng BIGINTcho trường ID, nhưng bạn có thực sự mong đợi để nắm bắt hơn 4.294.967.295 giá trị duy nhất không? Nếu vậy thì chỉ cần thay đổi trường thành BIGINT và thậm chí bạn có thể thay đổi giá trị hạt giống thành 0. Nhưng nếu không, bạn nên sử dụng INT và bắt đầu với giá trị tối thiểu để bạn có thể sử dụng toàn bộ phạm vi của kiểu dữ liệu đó .
  • Nếu muốn, bạn có thể thêm một hoặc nhiều Cột được tính không phân tích vào bảng này để trả về các biểu diễn văn bản của IPAddress.
  • Các trường Nhóm * được sắp xếp có chủ đích đi xuống , từ 8 đến 1, trong bảng để thực hiện SELECT *sẽ trả về các trường theo thứ tự dự kiến. Nhưng chỉ số có chúng tăng lên , từ 1 đến 8, vì đó là cách chúng được điền vào.
  • Một ví dụ (chưa hoàn thành) của một cột được tính toán để biểu thị các giá trị ở dạng văn bản là:

    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
    );

    Kiểm tra:

    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];

    Kết quả:

    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

Đối với SQL Server 2005, việc xác định các cột là VARDECIMALhết VARBINARYDATA_COMPRESSIONkhông có sẵn?
Matt

@SolomonRutzky Cảm ơn bạn đã giải thích chi tiết. Tôi tò mò, làm thế nào tôi có thể tìm kiếm giữa các phạm vi địa chỉ? Ví dụ: tôi có một nhà cung cấp dữ liệu cung cấp dữ liệu Định vị địa lý IP dưới dạng địa chỉ IP bắt đầu và kết thúc. Tôi cần tìm phạm vi mà một IP nhất định rơi vào.
J Weezy

@JWeezy Bạn được chào đón :). Làm thế nào là địa chỉ IP bắt đầu và kết thúc đang được lưu trữ? Bạn đang sử dụng địa chỉ IPv4 hoặc v6?
Solomon Rutzky

@SolomonRutzky Cả. IPv4 không phải là vấn đề vì tôi có thể lưu trữ dưới dạng số nguyên. Thật không may, không có kiểu dữ liệu liên quan đến số nguyên hoặc số 128 bit trong SQL Server đủ lớn để xử lý nó. Vì vậy, đối với IPv6, tôi đang lưu trữ nó trong VARBINARY (16) và sau đó tôi sử dụng toán tử BETCHEN để tìm kiếm giữa các phạm vi. Nhưng, tôi đang nhận được nhiều kết quả trên dải IP, điều mà tôi không nghĩ là chính xác. Tôi muốn sử dụng cùng loại dữ liệu cho cả IPv4 và IPv6 nếu có thể.
J Weeklyzy

@JWeezy Tôi sẽ đề nghị BINARY(16);-). Bạn có thể vui lòng cho tôi một ví dụ với phạm vi bắt đầu / kết thúc và ít nhất hai hàng bạn nhận lại, một hàng hợp lệ và ít nhất một hàng không hợp lệ không? Có thể là VARbinary rút ngắn một số giá trị.
Solomon Rutzky

1

Nhỏ hơn sẽ luôn luôn nhanh hơn. Với các giá trị nhỏ hơn, bạn có thể điều chỉnh nhiều hơn trong số chúng vào một trang, do đó ít IO hơn, có khả năng nông hơn B-Tree, v.v.

Tất cả những thứ khác (chi phí dịch thuật, khả năng đọc, khả năng tương thích, tải CPU, khả năng mở rộng chỉ mục, v.v.) là bằng nhau, tất nhiên.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.