IP adreslerinin saklanması - varchar (45) vs varbinary (16)


11

- Ben iki alan içeren bir tablo oluşturmak için gidiyorum IDolarak BIGINTve IPAddresssıra ya varchar(45)ya varbinary(16). Fikir, tüm benzersiz IP adreslerini saklamak ve diğer tablolarda IDgerçek yerine bir referans kullanmaktır IP address.

Genellikle, IDverilen IP addressveya (adres bulunmadıysa) dönen bir saklı yordam oluşturacağım adresi girin ve oluşturulan döndürür ID.

Birçok kayıt (tam olarak ne kadar söyleyemem) olmasını bekliyorum, ancak mümkün olduğunca hızlı yürütülmesi için yukarıdaki saklı yordam gerekir. Yani, gerçek IP adresini nasıl metin veya bayt biçiminde saklayacağımı merak ediyorum. Hangisi daha iyi olacak?

Zaten SQL CLRIP adres baytları dize ve ters dönüştürmek için fonksiyonları yazdım , bu yüzden dönüşüm bir sorun değil (hem IPv4ve ile çalışma IPv6).

Aramayı optimize etmek için bir dizin oluşturmam gerekiyor, ancak IP addressalanı kümelenmiş dizine dahil etmeli miyim , yoksa ayrı bir dizin oluşturmalı ve hangi türle arama daha hızlı olacak?


2
En azından IPv4 için neden 4 küçük nokta olmasın? O zaman aslında insan tarafından okunabilirler ve herhangi bir dönüşüm gerçekleştirmeniz gerekmez. Belirli arama türlerini (tam eşleme, alt ağ, vb.) Temsil etmek için her türlü kalıcı hesaplanmış sütun da oluşturabilirsiniz.
Aaron Bertrand

Eğer durum sadece IPv4sanırım adresi dönüştürür INTve alanı indeks anahtarı olarak kullanırdım. Ama IPv6iki BIGINTalan kullanmam gerekiyor ve değeri bir alanda depolamayı tercih ediyorum - bana daha doğal geliyor.
gotqn

1
Hala 4 TINYINT yerine neden INT'yi anlamıyor musunuz? Aynı depolama alanı, daha kolay hata ayıklama, daha az saçma, IMHO. Farklı doğrulama ve anlamlara sahip tamamen farklı iki türünüz varsa, neden aynı sütunu kullanmaları gerekir? Tek bir sütunun daha basit olduğunu iddia ediyorsanız, neden sadece SQL_VARIANT kullanmıyorsunuz, o zaman hiçbir şey için endişelenmenize gerek yok. Tarihleri, karakter dizilerini ve sayıları saklayabilirsiniz ve herkes devasa, yararsız bir sütunda büyük bir parti yapabilir ...
Aaron Bertrand

IP Adresleri nereden geliyor? Maskeyi / alt ağı (yani 10.10.10.1/124) içerecekler mi? Bunun web sunucusu günlüklerinden geldiğini gördüm ve BIGINT'e kolayca tercüme etmedim (tabii ki 0'ın -2.14xxxx milyar olduğunu varsaymak için normalleştirmeyi dahil etmedikçe, INT hesaplama imzasız bir INT gerektirdiği için çalışmaz). Alt ağ maskesi sadece ek bir TINYINT alanı olabilir sanırım. Ama bunları haritalamak için bir enlem / boylam DB ile eşleştirmek istiyorsanız BÜYÜK olarak saklamak isteyen anlıyorum. Ancak Aaron'un belirttiği gibi, bu kalıcı bir hesaplanmış col olabilir.
Solomon Rutzky

Yanıtlar:


13

gerçek IP adresinin nasıl kaydedileceği - metin veya bayt biçiminde. Hangisi daha iyi olacak?

Buradaki "metin" VARCHAR(45)ve "bayt" anlamına geldiği için şunu VARBINARY(16)söyleyebilirim: hiçbiri .

Aşağıdaki bilgiler verildiğinde ( IPv6 hakkındaki Wikipedia makalesinden ):

Adres gösterimi
IPv6 adresinin 128 biti, her biri 16 bitlik 8 grupta temsil edilir. Her grup 4 onaltılık basamak olarak yazılır ve gruplar iki nokta üst üste (:) ile ayrılır. 2001: 0db8: 0000: 0000: 0000: ff00: 0042: 8329 adresi bu gösterime bir örnektir.

Kolaylık olması açısından, bir IPv6 adresi, mümkünse aşağıdaki kuralların uygulanmasıyla daha kısa gösterimlerle kısaltılabilir.

  • Herhangi bir onaltılık basamak grubundan bir veya daha fazla önde gelen sıfır çıkarılır; bu genellikle baştaki sıfırların tümüne ya da hiçbirine yapılır. Örneğin, 0042 grubu 42'ye dönüştürülür.
  • Art arda sıfır bölümleri, iki nokta üst üste (: :) ile değiştirilir. Çift kolon, bir adreste yalnızca bir kez kullanılabilir, çünkü çoklu kullanım adresi belirsiz hale getirir. RFC 5952, atlanmış tek bir sıfır bölümünü belirtmek için çift kolon kullanılmaması gerektiğini önermektedir. [41]

Bu kuralların uygulanmasına bir örnek:

        Başlangıç ​​adresi: 2001: 0db8: 0000: 0000: 0000: ff00: 0042: 8329
        Her gruptaki tüm önde gelen sıfırları kaldırdıktan sonra: 2001: db8: 0: 0: 0: ff00: 42: 8329
        Ardışık sıfır bölümlerini çıkardıktan sonra: 2001 : db8 :: ff00: 42: 8329

VARBINARY(2)8 grubu temsil etmek için 8 alan kullanarak başlayacağım . Grup 5 - 8 için alanlar NULL, yalnızca IPv6 adresleri için kullanılacakları gibi olmalıdır . Grup 1 - 4 için alanlar NOT NULLhem IPv4 hem de IPv6 adresleri için kullanılacak şekilde olmalıdır .

Her grubu bağımsız tutarak (onları a VARCHAR(45)veya a VARBINARY(16)veya hatta iki BIGINTalana birleştirmenin aksine ) iki ana avantaj elde edersiniz:

  1. Adresi herhangi bir temsilde yeniden yapılandırmak çok daha kolaydır. Aksi takdirde, ardışık sıfır gruplarını (: :) ile değiştirmek için onu ayrıştırmanız gerekir. Onları ayrı tutmak, basit IF/ IIF/ CASEifadelerin bunu kolaylaştırmasını sağlar.
  2. Ya etkinleştirerek IPv6 adresleri alan bir ton kazandıracak ROW COMPRESSIONya PAGE COMPRESSION. Her iki COMPRESSION türü de 0x000 bayt alan alanlara izin vereceğinden , bu sıfır gruplarının tümü artık size hiçbir ücret ödemeyecektir. Öte yandan, örnek adresi yukarıdan (Wikipedia alıntısında) sakladıysanız, ortadaki tüm sıfırların 3 seti tam alan kaplar (eğer yapmadıkça VARCHAR(45)ve azaltılmış gösterim ile gitmediyseniz) , ancak endeksleme için iyi çalışmayabilir ve tam biçime yeniden yapılandırmak için özel ayrıştırma gerektirecektir, bu yüzden bunun bir seçenek olmadığını varsayalım ;-).

Ağı yakalamanız gerekiyorsa TINYINT, um, [Network]:-) adında bir alan oluşturun.

Ağ değeri hakkında daha fazla bilgi için, IPv6 adresindeki başka bir Wikipedia makalesinden bazı bilgiler :

Ağlar

Bir IPv6 ağı, ikisinin gücü olan bir boyutta bitişik IPv6 adresleri grubu olan bir adres bloğu kullanır. Adreslerin önde gelen bitleri kümesi, belirli bir ağdaki tüm ana makineler için aynıdır ve ağın adresi veya yönlendirme öneki olarak adlandırılır .

Ağ adres aralıkları CIDR gösterimi ile yazılmıştır. Bir ağ, bloktaki ilk adresle (tüm sıfırlarla biten), eğik çizgi (/) ve önekin bitlerindeki boyuta eşit ondalık bir değerle gösterilir. Örneğin, 2001: db8: 1234 :: / 48 olarak yazılan ağ 2001: db8: 1234: 0000: 0000: 0000: 0000: 0000 adresinde başlar ve 2001: db8: 1234: ffff: ffff: ffff: ffff'de biter : ffff.

Bir arabirim adresinin yönlendirme öneki doğrudan CIDR gösterimi ile adresle belirtilebilir. Örneğin, 2001: db8: a :: 123 adresi olan ve 2001: db8: a :: / 64 alt ağına bağlı bir arabirimin yapılandırması 2001: db8: a :: 123/64 olarak yazılır.


İndeksleme için, 8 Grup alanında bir Kümelenmemiş dizin ve buna dahil etmeye karar verirseniz muhtemelen Ağ alanı oluşturmayı söyleyebilirim.


Sonuç aşağıdaki gibi olmalıdır:

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

Notlar:

  • BIGINTKimlik alanı için kullanmayı planladığınızın farkındayım , ancak 4.294.967.295'den fazla benzersiz değer yakalamayı gerçekten bekliyor musunuz? Öyleyse, alanı BÜYÜK olarak değiştirin ve tohum değerini 0 olarak bile değiştirebilirsiniz. Aksi takdirde, INT'yi kullanarak ve minimum değerle başlayarak daha iyi olursunuz, böylece bu veri türünün tüm aralığını kullanabilirsiniz. .
  • İsterseniz, IPAdresi'nin metin temsillerini döndürmek için bu tabloya bir veya daha fazla Desteklenmeyen Hesaplanmış Sütun ekleyebilirsiniz.
  • Grup * alanları amaçlı olarak tablodan 8'den 1'e inilerek düzenlenir , böylece yapmak SELECT *alanları beklenen sırada döndürür. Ama endeks onları gidiş var kadar onlar dışarı nasıl doldurulduğunu olduğu gibi, 1'den 8'e kadar.
  • Metin biçimindeki değerleri temsil eden hesaplanmış bir sütunun örneği (bitmemiş):

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

    Ölçek:

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

    Sonuç:

    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 Server 2005 için, sütunları tanımlayan olur VARDECIMALüzerinde VARBINARYberi DATA_COMPRESSIONmevcut değildir?
Matt Borja

@SolomonRutzky Detaylı açıklama için teşekkür ederim. Merak ediyorum, adres aralıkları arasında nasıl arama yaparım? Örneğin, bir başlangıç ​​ve bitiş IP adresi şeklinde IP Coğrafi Konum verileri sağlayan bir veri satıcısı var. Belirli bir IP'nin hangi aralıkta olduğunu bulmam gerekiyor.
J Weezy

@JWeezy Rica ederim :). Başlangıç ​​ve bitiş IP adresleri nasıl saklanıyor? IPv4 veya v6 adresleri mi kullanıyorsunuz?
Solomon Rutzky

@SolomonRutzky İkisi de. IPv4 bir sorun değil çünkü bir tamsayı olarak saklayabilirim. Ne yazık ki, SQL Server'da işlemek için yeterince büyük 128 bit tam sayı veya sayı ile ilgili veri türü yoktur. IPv6 için VARBINARY (16) 'da saklıyorum ve sonra aralıklar arasında arama yapmak için BETWEEN işlecini kullanıyorum. Ancak, IP aralıklarında doğru olduğunu düşünmediğim birden fazla sonuç alıyorum. Mümkünse IPv4 ve IPv6 için aynı veri türünü kullanmak istiyorum.
J Weezy

@JWeezy BINARY(16);-) önerecektim . Lütfen bir başlangıç ​​/ bitiş aralığı ve geri aldığınız en az iki satır, bir geçerli ve en az bir geçersiz olan bir örnek verebilir misiniz? VARbinary'nin bazı değerleri kısaltması olabilir.
Solomon Rutzky

1

Daha küçük her zaman daha hızlı olacaktır. Daha küçük değerlerle daha fazlasını tek bir sayfaya sığdırabilirsiniz, bu nedenle daha az IO, potansiyel olarak daha sığ B Ağaçları vb.

Elbette, diğer tüm şeyler (çeviri yükü, okunabilirlik, uyumluluk, CPU yükü, dizin uyumu vb.) Eşittir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.