การจัดเก็บที่อยู่ IP - varchar (45) vs varbinary (16)


11

ฉันจะสร้างตารางที่มีสองช่อง - IDเป็นBIGINTและIPAddressเป็นอย่างใดอย่างหนึ่งหรือvarchar(45) varbinary(16)แนวคิดคือการจัดเก็บที่อยู่ IP ที่ไม่ซ้ำกันทั้งหมดและใช้การอ้างอิงIDแทนจริงIP addressในตารางอื่น ๆ

โดยทั่วไปฉันจะสร้างการจัดเก็บที่กลับมาIDสำหรับการรับIP addressหรือ (ถ้าอยู่ไม่พบ) IDแทรกอยู่และกลับที่สร้างขึ้น

ฉันคาดหวังว่าจะมีบันทึกจำนวนมาก (ฉันไม่สามารถบอกได้อย่างชัดเจนว่ามีจำนวนเท่าใด) แต่ฉันต้องการขั้นตอนการจัดเก็บด้านบนเพื่อดำเนินการโดยเร็วที่สุด ดังนั้นฉันจึงสงสัยว่าจะเก็บที่อยู่ IP จริงไว้อย่างไรในรูปแบบข้อความหรือไบต์ อันไหนจะดีกว่ากัน?

ฉันได้เขียนSQL CLRฟังก์ชันสำหรับแปลงที่อยู่ IP เป็นสตริงและย้อนกลับดังนั้นการแปลงจึงไม่ใช่ปัญหา (ทำงานกับทั้งสองIPv4และIPv6)

ฉันเดาว่าฉันต้องสร้างดัชนีเพื่อปรับการค้นหาให้เหมาะสม แต่ฉันไม่แน่ใจว่าฉันควรรวมIP addressฟิลด์ไว้ในดัชนีกลุ่มหรือเพื่อสร้างดัชนีแยกต่างหากและการค้นหาประเภทใดจะเร็วขึ้น


2
สำหรับ IPv4 เป็นอย่างน้อยทำไมถึงไม่ใช่จิ๋ว 4 อัน? จริงๆแล้วมันเป็นมนุษย์ที่อ่านได้และคุณไม่ต้องทำการแปลงใด ๆ นอกจากนี้คุณยังสามารถสร้างคอลัมน์ที่คำนวณได้ทุกประเภทเพื่อแสดงประเภทการค้นหาเฉพาะ (การจับคู่ที่ตรงกันซับเน็ต ฯลฯ )
Aaron Bertrand

หากเป็นกรณีสำหรับIPv4ฉันเดาฉันจะแปลงที่อยู่INTและใช้ฟิลด์เป็นคีย์ดัชนี แต่สำหรับIPv6ฉันต้องใช้สองBIGINTเขตข้อมูลและฉันต้องการเก็บค่าไว้ในเขตข้อมูลเดียว - ดูเหมือนว่าฉันจะเป็นธรรมชาติมากขึ้น
gotqn

1
ยังไม่เข้าใจว่าทำไม INT แทนที่จะเป็น 4 TINYINT ที่เก็บข้อมูลเดียวกันการดีบักง่ายขึ้นไร้สาระน้อยกว่า IMHO หากคุณมีสองประเภทที่แตกต่างกันโดยสิ้นเชิงกับการตรวจสอบและความหมายที่แตกต่างกันทำไมพวกเขาต้องใช้คอลัมน์เดียวกัน หากคุณกำลังเดิมพันว่าคอลัมน์เดียวนั้นง่ายกว่าทำไมไม่ใช้ SQL_VARIANT เพียงอย่างเดียวคุณก็ไม่ต้องกังวลอะไรอีก คุณสามารถจัดเก็บวันที่และสตริงและตัวเลขและทุกคนสามารถมีงานปาร์ตี้ขนาดใหญ่ในคอลัมน์ขนาดมหึมาที่ไร้ประโยชน์ ...
Aaron Bertrand

ที่อยู่ IP มาจากไหน พวกเขาจะรวมหน้ากาก / ซับเน็ต (เช่น 10.10.10.1/124) หรือไม่? ฉันได้เห็นสิ่งนี้มาจากบันทึกการใช้เว็บเซิร์ฟเวอร์และไม่สามารถแปลเป็น BIGINT ได้อย่างง่ายดาย (INT จะไม่ทำงานเนื่องจากการคำนวณต้องใช้ INT ที่ไม่ได้ลงชื่อเว้นแต่ว่าแน่นอนว่าคุณได้รวมการทำให้ปกติเป็น 0 เป็น -2.14xxxx พันล้าน) ฉันเดาว่า subnet mask อาจเป็นฟิลด์ TINYINT เพิ่มเติม แต่ฉันเข้าใจว่าต้องการเก็บเป็น BIGINT หากต้องการจับคู่ที่ตรงกับค่าละติจูด / ลองจิจูดของแผนที่เพื่อจับคู่กับมัน แต่ดังที่แอรอนกล่าวไว้นั่นอาจเป็นคอลัมน์ที่คำนวณได้
โซโลมอน Rutzky

คำตอบ:


13

วิธีจัดเก็บที่อยู่ IP จริง - ในรูปแบบข้อความหรือไบต์ อันไหนจะดีกว่ากัน?

ตั้งแต่ "ข้อความ" ที่นี่หมายถึงVARCHAR(45)และ "ไบต์" หมายถึงVARBINARY(16)ผมจะบอกว่า: ค่า

รับข้อมูลต่อไปนี้ (จากบทความ Wikipedia บน IPv6 ):

การแสดงที่อยู่
128 บิตของที่อยู่ IPv6 จะถูกแสดงใน 8 กลุ่มจาก 16 บิตในแต่ละ แต่ละกลุ่มจะถูกเขียนเป็นเลขฐานสิบหก 4 หลักและกลุ่มจะถูกคั่นด้วยเครื่องหมายทวิภาค (:) ที่อยู่ 2001: 0db8: 0000: 0000: 0000: ff00: 0042: 8329 เป็นตัวอย่างของการเป็นตัวแทนนี้

เพื่อความสะดวก IPv6 address อาจถูกย่อให้สั้นลงโดยใช้กฎต่อไปนี้หากเป็นไปได้

  • ศูนย์นำหน้าตั้งแต่หนึ่งเลขขึ้นไปจากกลุ่มเลขฐานสิบหกใด ๆ จะถูกลบออก สิ่งนี้มักจะทำกับศูนย์ทั้งหมดที่นำหน้าหรือทั้งหมด ตัวอย่างเช่นกลุ่ม 0042 ถูกแปลงเป็น 42
  • ส่วนที่ต่อเนื่องกันเป็นศูนย์จะถูกแทนที่ด้วยเครื่องหมายโคลอนคู่ (: :) เครื่องหมายทวิภาคคู่สามารถใช้ได้เพียงครั้งเดียวในที่อยู่เนื่องจากการใช้หลายครั้งจะทำให้ที่อยู่ไม่ถูกกำหนด RFC 5952 แนะนำว่าต้องไม่ใช้เครื่องหมายโคลอนคู่เพื่อแสดงส่วนเดียวที่ละเว้นของเลขศูนย์ [41]

ตัวอย่างของการใช้กฎเหล่านี้:

        ที่อยู่เริ่มต้น: 2001: 0db8: 0000: 0000: 0000: ff00: 0042: 8329
        หลังจากลบศูนย์นำหน้าทั้งหมดในแต่ละกลุ่ม: 2001: db8: 0: 0: 0: ff00: 42: 8329
        หลังจากตัดส่วนติดต่อกันเป็นศูนย์: 2001 : db8 :: ff00: 42: 8329

ฉันจะเริ่มต้นด้วยการใช้ 8 VARBINARY(2)เขตข้อมูลเพื่อแสดงถึง 8 กลุ่ม ฟิลด์สำหรับกลุ่ม 5 - 8 ควรเป็นNULLเพราะจะใช้สำหรับที่อยู่ IPv6 เท่านั้น ฟิลด์สำหรับกลุ่ม 1 - 4 ควรเป็นฟิลด์ที่NOT NULLจะใช้สำหรับทั้งที่อยู่ IPv4 และ IPv6

โดยการทำให้แต่ละกลุ่มเป็นอิสระ (เมื่อเทียบกับการรวมพวกเขาลงในฟิลด์VARCHAR(45)หนึ่งVARBINARY(16)หรือสองBIGINTฟิลด์) คุณจะได้รับประโยชน์หลักสองประการ:

  1. มันง่ายกว่ามากในการสร้างที่อยู่ใหม่ให้เป็นตัวแทนเฉพาะ มิฉะนั้นเพื่อที่จะแทนที่กลุ่มศูนย์ต่อเนื่องด้วย (: :) คุณจะต้องแยกมันออก ทำให้พวกเขาแยกต่างหากช่วยให้ง่ายIF/ IIF/ CASEงบเพื่ออำนวยความสะดวกนี้
  2. คุณจะประหยัดตันของพื้นที่บนที่อยู่ IPv6 โดยการเปิดใช้งานอย่างใดอย่างหนึ่งหรือROW COMPRESSION PAGE COMPRESSIONเนื่องจากการบีบอัดทั้งสองประเภทจะอนุญาตให้ใช้เขตข้อมูลที่มี0x00ค่าเป็น 0 ไบต์กลุ่ม zeroes ทั้งหมดเหล่านี้จะไม่เสียค่าใช้จ่าย แต่อย่างใด ในทางกลับกันถ้าคุณเก็บตัวอย่างที่อยู่จากด้านบน (ในเครื่องหมายคำพูดของ Wikipedia) ดังนั้นศูนย์ 3 ชุดทั้งหมดที่อยู่ตรงกลางจะใช้พื้นที่จำนวนเต็มที่ (เว้นแต่คุณจะทำVARCHAR(45)และไปพร้อมกับสัญลักษณ์ที่ลดลง) แต่นั่นอาจไม่ทำงานได้ดีสำหรับการจัดทำดัชนีและจะต้องมีการแยกวิเคราะห์พิเศษเพื่อสร้างใหม่เป็นรูปแบบเต็มดังนั้นสมมติว่านั่นไม่ใช่ตัวเลือก ;-)

หากคุณต้องการจับเครือข่ายให้สร้างTINYINTเขตข้อมูลสำหรับที่เรียกว่าอืม[Network]:-)

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับค่าเครือข่ายนี่คือข้อมูลบางส่วนจากบทความ Wikipediaอื่นเกี่ยวกับที่อยู่ IPv6 :

เครือข่าย

เครือข่าย IPv6 ใช้บล็อกที่อยู่ซึ่งเป็นกลุ่มที่อยู่ติดกันของที่อยู่ IPv6 ที่มีขนาดเท่ากับกำลังสอง ชุดชั้นนำของบิตที่อยู่เหมือนกันสำหรับโฮสต์ทั้งหมดในเครือข่ายที่กำหนดและจะเรียกว่าอยู่หรือเส้นทางเครือข่ายของคำนำหน้า

ช่วงที่อยู่เครือข่ายถูกเขียนในรูปแบบ CIDR เครือข่ายจะแสดงโดยที่อยู่แรกในบล็อก (สิ้นสุดในศูนย์ทั้งหมด), เครื่องหมายทับ (/) และค่าทศนิยมเท่ากับขนาดในบิตของคำนำหน้า ตัวอย่างเช่นเครือข่ายที่เขียนเป็น 2001: db8: 1234 :: / 48 เริ่มต้นที่ที่อยู่ 2001: db8: 1234: 0000: 0000: 0000: 0000: 0000: 0000 และสิ้นสุดที่ 2001: db8: 1234: ffff: ffff: ffff: ffff : ffff

คำนำหน้าการกำหนดเส้นทางของที่อยู่อินเตอร์เฟสอาจระบุโดยตรงด้วยที่อยู่ด้วยสัญกรณ์ CIDR ตัวอย่างเช่นการกำหนดค่าของอินเทอร์เฟซที่มีที่อยู่ 2001: db8: a :: 123 เชื่อมต่อกับเครือข่ายย่อย 2001: db8: a :: / 64 ถูกเขียนเป็น 2001: db8: a :: 123/64


สำหรับการจัดทำดัชนีฉันจะบอกว่าสร้างดัชนีที่ไม่เป็นคลัสเตอร์ในเขตข้อมูล 8 Group และอาจเป็นไปได้ว่าเขตข้อมูลเครือข่ายหากคุณตัดสินใจที่จะรวมไว้


ผลลัพธ์ที่ได้ควรเป็นดังนี้:

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

หมายเหตุ:

  • ฉันรู้ว่าคุณวางแผนที่จะใช้BIGINTสำหรับฟิลด์ ID แต่คุณคาดหวังว่าจะได้รับค่าที่ไม่ซ้ำมากกว่า 4,294,967,295 หรือไม่ ถ้าเป็นเช่นนั้นให้เปลี่ยนเขตข้อมูลเป็น BIGINT และคุณสามารถเปลี่ยนค่าเมล็ดเป็น 0 ได้ แต่ไม่เช่นนั้นคุณควรใช้ INT และเริ่มต้นด้วยค่าต่ำสุดเพื่อให้คุณสามารถใช้ช่วงของประเภทข้อมูลนั้นได้ทั้งหมด .
  • หากต้องการคุณสามารถเพิ่มคอลัมน์ที่ไม่ได้รับการคำนวณอย่างน้อยหนึ่งคอลัมน์ในตารางนี้เพื่อส่งคืนการแทนข้อความของ IPAddress
  • เขตข้อมูลกลุ่ม * ถูกจัดเรียงอย่างตั้งใจลงไปจาก 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 Server 2005 จะกำหนดคอลัมน์ที่เป็นVARDECIMALมากกว่าVARBINARYเนื่องจากDATA_COMPRESSIONไม่สามารถใช้งาน?
Matt

@SolomonRutzky ขอบคุณสำหรับคำอธิบายโดยละเอียด ฉันสงสัยฉันจะค้นหาระหว่างช่วงที่อยู่ได้อย่างไร ตัวอย่างเช่นฉันมีผู้ให้บริการข้อมูลที่ให้ข้อมูลตำแหน่งทางภูมิศาสตร์ IP ในรูปแบบของที่อยู่ IP เริ่มต้นและปลายทาง ฉันต้องการค้นหาว่าช่วงใดที่ IP ที่ระบุตกอยู่ในนั้น
J Weezy

@JWeezy ยินดีต้อนรับ :) ที่อยู่ IP เริ่มต้นและปลายทางถูกจัดเก็บอย่างไร คุณใช้ที่อยู่ IPv4 หรือ v6 หรือไม่
โซโลมอน Rutzky

@SolomonRutzky ทั้งคู่ IPv4 ไม่ใช่ปัญหาเพราะฉันสามารถเก็บเป็นจำนวนเต็มได้ แต่น่าเสียดายที่ไม่มีจำนวนเต็ม 128 บิตหรือจำนวนข้อมูลที่เกี่ยวข้องใน SQL Server ที่มีขนาดใหญ่พอที่จะรองรับได้ ดังนั้นสำหรับ IPv6 ฉันกำลังเก็บมันไว้ใน VARBINARY (16) จากนั้นฉันก็ใช้โอเปอเรเตอร์ BETWEEN เพื่อค้นหาระหว่างช่วงต่างๆ แต่ฉันได้รับผลลัพธ์หลายรายการในช่วง IP ซึ่งฉันคิดว่าไม่ถูกต้อง ฉันต้องการใช้ชนิดข้อมูลเดียวกันสำหรับทั้ง IPv4 และ IPv6 ถ้าเป็นไปได้
J Weezy

@JWeezy ฉันจะแนะนำBINARY(16);-) คุณช่วยยกตัวอย่างให้ฉันด้วยช่วงเริ่มต้น / สิ้นสุดและอย่างน้อยสองแถวที่คุณได้รับกลับมาใช้ได้หนึ่งครั้งและอย่างน้อยหนึ่งรายการที่ไม่ถูกต้อง อาจเป็นได้ว่า VARbinary จะทำให้ค่าบางค่าสั้นลง
โซโลมอน Rutzky

1

เล็กกว่าจะเร็วกว่าเสมอ ด้วยค่าที่น้อยลงคุณสามารถใส่มันลงในหน้าเดียวได้ดังนั้น IO น้อยลงและอาจทำให้ B-Trees ตื้นขึ้นเป็นต้น

ทุกสิ่งอื่น ๆ (ค่าใช้จ่ายในการแปล, ความสามารถในการอ่าน, ความเข้ากันได้, โหลดซีพียู, ความสามารถในการระบุดัชนี ฯลฯ ) เท่ากันแน่นอน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.