Menyimpan alamat IP - varchar (45) vs varbinary (16)

11

Saya akan membuat tabel dengan dua bidang - IDsebagai BIGINTdan IPAddresssebagai varchar(45)atau varbinary(16). Idenya adalah untuk menyimpan semua alamat IP unik dan menggunakan referensi IDbukan yang sebenarnya IP addressdi tabel lain.

Secara umum, saya akan membuat prosedur tersimpan yang mengembalikan yang IDdiberikan IP addressatau (jika alamat tidak ditemukan) masukkan alamat dan kembalikan yang dihasilkan ID.

Saya berharap memiliki banyak catatan (saya tidak tahu persis berapa banyak), tetapi saya perlu prosedur tersimpan di atas untuk dieksekusi secepat mungkin. Jadi, saya bertanya-tanya bagaimana cara menyimpan alamat IP yang sebenarnya - dalam format teks atau byte. Mana yang lebih baik?

Saya sudah menulis SQL CLRfungsi untuk mengubah byte alamat IP menjadi string dan sebaliknya, jadi transformasi bukanlah masalah (bekerja dengan keduanya IPv4dan IPv6).

Saya kira saya perlu membuat indeks untuk mengoptimalkan pencarian, tetapi saya tidak yakin apakah saya harus memasukkan IP addressbidang ke indeks berkerumun, atau untuk membuat indeks terpisah dan dengan jenis pencarian yang akan lebih cepat?

Gotqn
sumber
2
Setidaknya untuk IPv4, mengapa tidak 4 tinyints? Maka mereka benar-benar dapat dibaca manusia dan Anda tidak perlu melakukan konversi apa pun. Anda juga dapat membuat semua jenis kolom yang dihitung terus-menerus untuk mewakili jenis pencarian tertentu (kecocokan persis, subnet, dll.).
Aaron Bertrand
Jika itu hanya karena IPv4saya kira saya akan mengkonversi alamat INTdan menggunakan bidang sebagai kunci indeks. Tetapi untuk IPv6saya perlu menggunakan dua BIGINTbidang dan saya lebih suka menyimpan nilai dalam satu bidang - bagi saya lebih alami.
Gotqn
1
Masih tidak mengerti mengapa INT bukan 4 TINYINT? Penyimpanan yang sama, debugging lebih mudah, lebih sedikit omong kosong, IMHO. Jika Anda memiliki dua tipe yang sama sekali berbeda dengan validasi dan makna yang berbeda, mengapa mereka perlu menggunakan kolom yang sama? Jika Anda bertaruh bahwa satu kolom lebih sederhana, mengapa tidak hanya menggunakan SQL_VARIANT, maka Anda tidak perlu khawatir tentang apa pun. Anda dapat menyimpan tanggal dan string serta angka dan semua orang dapat mengadakan pesta besar dalam satu kolom raksasa yang tidak berguna ...
Aaron Bertrand
Dari mana alamat IP berasal? Apakah mereka akan menyertakan mask / subnet (yaitu 10.10.10.1/124)? Saya telah melihat ini datang dari log server web dan tidak menerjemahkan dengan mudah ke BIGINT (INT tidak akan berfungsi karena perhitungannya memerlukan INT yang tidak ditandatangani, kecuali tentu saja, Anda memasukkan normalisasi untuk menganggap 0 benar-benar -2,14xxxx miliar). Saya kira subnet mask bisa menjadi bidang TINYINT tambahan. Tapi saya mengerti ingin menyimpan sebagai BIGINT jika ingin mencocokkannya dengan DB lintang / bujur untuk memetakannya. Tapi seperti yang Harun katakan, itu bisa menjadi col yang dihitung terus-menerus.
Solomon Rutzky

Jawaban:

12

cara menyimpan alamat IP aktual - dalam format teks atau byte. Mana yang lebih baik?

Karena "teks" di sini merujuk ke VARCHAR(45)dan "byte" merujuk VARBINARY(16), saya akan mengatakan: keduanya .

Diberikan informasi berikut (dari artikel Wikipedia tentang IPv6 ):

Representasi alamat
128 bit alamat IPv6 direpresentasikan dalam 8 grup masing-masing 16 bit. Setiap grup ditulis sebagai 4 digit heksadesimal dan grup dipisahkan oleh titik dua (:). Alamat 2001: 0db8: 0000: 0000: 0000: ff00: 0042: 8329 adalah contoh dari representasi ini.

Untuk kenyamanan, alamat IPv6 dapat disingkat menjadi notasi yang lebih pendek dengan penerapan aturan berikut, jika memungkinkan.

  • Satu atau lebih nol nol terkemuka dari grup mana saja dari angka heksadesimal dihilangkan; ini biasanya dilakukan untuk semua atau tidak ada nol terkemuka. Misalnya, grup 0042 diubah menjadi 42.
  • Bagian nol yang berurutan diganti dengan tanda titik dua (: :). Kolon ganda hanya dapat digunakan satu kali dalam satu alamat, karena banyak penggunaan akan membuat alamat tersebut tidak pasti. RFC 5952 merekomendasikan bahwa titik dua tidak boleh digunakan untuk menunjukkan satu bagian nol yang dihilangkan. [41]

Contoh penerapan aturan ini:

        Alamat awal: 2001: 0db8: 0000: 0000: 0000: ff00: 0042: 8329
        Setelah menghapus semua nol terkemuka di setiap grup: 2001: db8: 0: 0: 0: ff00: 42: 8329
        Setelah menghilangkan bagian berturut-turut dari nol: 2001 : db8 :: ff00: 42: 8329

Saya akan mulai dengan menggunakan 8 VARBINARY(2)bidang untuk mewakili 8 grup. Bidang untuk Grup 5 - 8 harus NULLkarena hanya akan digunakan untuk alamat IPv6. Bidang untuk Grup 1 - 4 harus NOT NULLseperti yang akan digunakan untuk alamat IPv4 dan IPv6.

Dengan menjaga masing-masing kelompok mandiri (sebagai lawan menggabungkan mereka ke dalam satu VARCHAR(45)atau dua VARBINARY(16)atau bahkan dua BIGINTbidang) Anda mendapatkan dua manfaat utama:

  1. Jauh lebih mudah untuk merekonstruksi alamat menjadi representasi tertentu. Jika tidak, untuk mengganti kelompok nol berturut-turut dengan (: :) Anda harus menguraikannya. Memisahkannya memungkinkan IF/ IIF/ CASEpernyataan sederhana untuk memfasilitasi ini.
  2. Anda akan menghemat banyak ruang pada alamat IPv6 dengan mengaktifkan salah satu ROW COMPRESSIONatau PAGE COMPRESSION. Karena kedua jenis KOMPRESI akan memungkinkan untuk bidang yang 0x00mengambil 0 byte, semua kelompok nol itu sekarang tidak akan dikenakan biaya apa pun. Di sisi lain, jika Anda menyimpan contoh alamat dari atas (dalam kutipan Wikipedia), maka 3 set semua nol di tengah akan mengambil jumlah penuh ruang mereka (kecuali jika Anda melakukan VARCHAR(45)dan pergi dengan notasi berkurang) , tapi itu mungkin tidak berfungsi dengan baik untuk pengindeksan dan akan membutuhkan parsing khusus untuk merekonstruksi ke format penuh, jadi mari kita asumsikan itu bukan pilihan ;-).

JIKA Anda perlu menangkap Jaringan, buat TINYINTbidang untuk yang disebut, um, [Network]:-)

Untuk info lebih lanjut tentang nilai Jaringan, berikut adalah beberapa info dari artikel Wikipedia lain di alamat IPv6 :

Jaringan

Jaringan IPv6 menggunakan blok alamat yang merupakan kelompok yang berdekatan dari alamat IPv6 dengan ukuran yang merupakan kekuatan dua. Seperangkat bit alamat yang terkemuka identik untuk semua host di jaringan tertentu, dan disebut alamat jaringan atau awalan perutean .

Rentang alamat jaringan ditulis dalam notasi CIDR. Jaringan dilambangkan dengan alamat pertama di blok (diakhiri dengan semua nol), garis miring (/), dan nilai desimal sama dengan ukuran dalam bit awalan. Misalnya, jaringan yang ditulis sebagai 2001: db8: 1234 :: / 48 dimulai pada alamat 2001: db8: 1234: 0000: 0000: 0000: 0000: 0000 dan berakhir pada tahun 2001: db8: 1234: ffff: ffff: ffff: ffff : ffff.

Awalan perutean alamat antarmuka dapat secara langsung ditunjukkan dengan alamat dengan notasi CIDR. Misalnya, konfigurasi antarmuka dengan alamat 2001: db8: a :: 123 terhubung ke subnet 2001: db8: a :: / 64 ditulis sebagai 2001: db8: a :: 123/64.


Untuk pengindeksan, saya akan mengatakan membuat indeks Non-Clustered pada bidang 8 Grup, dan mungkin bidang Jaringan jika Anda memutuskan untuk memasukkan itu.


Hasil akhirnya harus seperti ini:

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

Catatan:

  • Saya menyadari bahwa Anda berencana menggunakan BIGINTuntuk bidang ID, tetapi apakah Anda benar-benar berharap untuk mendapatkan lebih dari 4.294.967.295 nilai unik? Jika demikian maka ubah saja bidang menjadi BIGINT dan Anda bahkan dapat mengubah nilai seed menjadi 0. Tetapi sebaliknya Anda lebih baik menggunakan INT dan mulai dengan nilai minimum sehingga Anda dapat menggunakan seluruh rentang datatype tersebut. .
  • Jika diinginkan, Anda bisa menambahkan satu atau lebih Kolom yang Dihitung yang Dilarang NON ke tabel ini untuk mengembalikan representasi teks dari IPAddress.
  • Kolom Grup * diatur dengan sengaja turun , dari 8 ke 1, di tabel sehingga tindakan SELECT *akan mengembalikan bidang dalam urutan yang diharapkan. Tapi indeks membuat mereka naik , dari 1 menjadi 8, karena begitulah mereka diisi.
  • Contoh (belum selesai) dari kolom yang dihitung untuk mewakili nilai-nilai dalam bentuk teks adalah:

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

    Uji:

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

    Hasil:

    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
Solomon Rutzky
sumber
Untuk SQL Server 2005, apakah mendefinisikan kolom sebagai VARDECIMALselesai VARBINARYkarena DATA_COMPRESSIONtidak tersedia?
Matt
@SolomonRutzky Terima kasih atas penjelasan terperincinya. Saya ingin tahu, bagaimana cara mencari di antara rentang alamat? Sebagai contoh, saya memiliki vendor data yang menyediakan data IP Geolocation dalam bentuk alamat IP awal dan akhir. Saya perlu menemukan kisaran IP mana yang diberikan.
J Weezy
@ Joeezy Anda dipersilakan :). Bagaimana awal dan akhir alamat IP disimpan? Apakah Anda menggunakan alamat IPv4 atau v6?
Solomon Rutzky
@SolomonRutzky Keduanya. IPv4 bukan masalah karena saya bisa menyimpannya sebagai integer. Sayangnya, tidak ada integer 128 bit atau nomor tipe data terkait dalam SQL Server yang cukup besar untuk menanganinya. Jadi, untuk IPv6 saya menyimpannya di VARBINARY (16) dan kemudian saya menggunakan operator BETWEEN untuk mencari di antara rentang. Tapi, saya mendapatkan beberapa hasil pada rentang IP, yang saya pikir tidak benar. Saya ingin menggunakan tipe data yang sama untuk IPv4 dan IPv6 jika memungkinkan.
J Weezy
@JWeezy saya akan menyarankan BINARY(16);-). Bisakah Anda memberi saya contoh dengan rentang awal / akhir dan setidaknya dua baris yang Anda dapatkan kembali, satu valid dan setidaknya satu tidak valid? Mungkin VARbinary mempersingkat beberapa nilai.
Solomon Rutzky
1

Lebih kecil selalu akan lebih cepat. Dengan nilai yang lebih kecil Anda dapat memasukkan lebih banyak dari mereka ke dalam satu halaman, sehingga lebih sedikit IO, berpotensi lebih dangkal B-Trees dll.

Semua hal lain (terjemahan overhead, keterbacaan, kompatibilitas, beban CPU, indeks sargabilitas dll) sama, tentu saja.

Michael Green
sumber