เปลี่ยนคีย์หลักจาก IDENTITY เป็นคอลัมน์ที่คำนวณแล้วโดยใช้ COALESCE


10

ในความพยายามที่จะแยกแอปพลิเคชันออกจากฐานข้อมูลเสาหินของเราเราได้พยายามเปลี่ยนคอลัมน์ INT IDENTITY ของตารางต่างๆให้เป็นคอลัมน์ที่คำนวณแบบ PERSISTED ที่ใช้ COALESCE โดยพื้นฐานแล้วเราต้องการแอปพลิเคชั่นแยกสองตัวที่ยังคงสามารถอัปเดตฐานข้อมูลสำหรับข้อมูลทั่วไปที่แชร์ในแอปพลิเคชันจำนวนมากในขณะที่ยังคงอนุญาตให้แอปพลิเคชันที่มีอยู่สร้างข้อมูลในตารางเหล่านี้โดยไม่ต้องใช้

โดยพื้นฐานแล้วเราได้ย้ายจากคำจำกัดความคอลัมน์ของ;

PkId INT IDENTITY(1,1) PRIMARY KEY

ถึง;

PkId AS AS COALESCE(old_id, external_id, new_id) PERSISTED NOT NULL,
old_id INT NULL, -- Values here are from existing records of PkId before table change
external_id INT NULL,
new_id INT IDENTITY(2000000,1) NOT NULL

ในทุกกรณี PkId ยังเป็นคีย์หลักและในทุกกรณียกเว้นเพียงกรณีเดียวก็คือ CLUSTERED ตารางทั้งหมดมีคีย์และดัชนีต่างประเทศเหมือนเดิม โดยพื้นฐานแล้วรูปแบบใหม่อนุญาตให้ใช้ PkId โดยแอปพลิเคชัน decoupled (เป็น external_id) แต่ยังอนุญาตให้ PkId เป็นค่าคอลัมน์ประจำตัวดังนั้นจึงอนุญาตให้ใช้รหัสที่มีอยู่ซึ่งอาศัยคอลัมน์ SCENTE_IDENTITY และ @@ IDENTITY ในการทำงานตามที่เคย

ปัญหาที่เราเจอก็คือเราได้เจอคำถามสองสามข้อที่เคยทำงานในเวลาที่ยอมรับได้เพื่อให้เกิดการระเบิดอย่างสมบูรณ์ แผนการสืบค้นที่สร้างขึ้นโดยใช้แบบสอบถามเหล่านี้จะไม่เหมือนกับสิ่งที่เคยเป็นมาก่อน

เนื่องจากคอลัมน์ใหม่เป็นคีย์หลักชนิดข้อมูลเหมือนเดิมและมีส่วนร่วมฉันจะมีคิวรีและแผนคิวรีที่คาดว่าจะทำงานเหมือนกับที่เคยทำมาก่อน COMPUTED PERSISTED INT PkId ควรทำตัวเป็นแบบเดียวกับคำจำกัดความ INT อย่างชัดเจนในแง่ของวิธีที่ SQL Server จะสร้างแผนการดำเนินการหรือไม่ มีปัญหาอื่น ๆ อีกหรือไม่ที่คุณสามารถมองเห็นได้

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


ความคิดเห็นไม่ได้มีไว้สำหรับการอภิปรายเพิ่มเติม การสนทนานี้ได้รับการย้ายไปแชท
พอลไวท์ 9

คำตอบ:


4

FIRST

old_idคุณอาจไม่จำเป็นต้องทั้งหมดสามคอลัมน์: external_id, new_id, new_idคอลัมน์เป็นจะมีค่าใหม่ที่สร้างขึ้นสำหรับแต่ละแถวแม้เมื่อคุณใส่ลงในIDENTITY external_idแต่ระหว่างold_idและexternal_idเหล่านี้จะสวยมากพิเศษร่วมกันอย่างใดอย่างหนึ่งที่มีอยู่แล้วold_idคุ้มค่าหรือคอลัมน์ว่าในความคิดในปัจจุบันจะเป็นเพียงNULLถ้าใช้หรือexternal_id new_idเนื่องจากคุณจะไม่เพิ่ม id "ภายนอก" ใหม่ลงในแถวที่มีอยู่แล้ว (เช่นที่มีold_idค่า) และจะไม่มีค่าใหม่เข้ามาold_idดังนั้นอาจมีหนึ่งคอลัมน์ที่ใช้ เพื่อวัตถุประสงค์ทั้งสอง

ดังนั้นกำจัดexternal_idคอลัมน์และเปลี่ยนชื่อold_idเป็นอะไรold_or_external_idหรืออะไรก็ตาม สิ่งนี้ไม่จำเป็นต้องมีการเปลี่ยนแปลงที่แท้จริงกับสิ่งใด แต่ลดความยุ่งยากบางอย่าง อย่างมากคุณอาจต้องเรียกคอลัมน์external_idแม้ว่าจะมีค่า "เก่า" หากโค้ดแอปถูกเขียนเพื่อแทรกเข้าไปexternal_idแล้ว

ที่ช่วยลดโครงสร้างใหม่ให้เป็นเพียง:

PkId AS AS COALESCE(old_or_external_id, new_id, -1) PERSISTED NOT NULL,
old_or_external_id INT NULL, -- values from existing record OR passed in from app
new_id INT IDENTITY(2000000, 1) NOT NULL

ตอนนี้คุณเพิ่ม 8 ไบต์ต่อแถวแทนที่จะเป็น 12 ไบต์เท่านั้น (สมมติว่าคุณไม่ได้ใช้SPARSEตัวเลือกหรือการบีบอัดข้อมูล) และคุณไม่จำเป็นต้องเปลี่ยนรหัสใด ๆ T-SQL หรือรหัสแอป

SECOND

ต่อไปตามเส้นทางของการทำให้เข้าใจง่ายนี้เรามาดูสิ่งที่เราได้จากไป:

  • คอลัมน์ทั้งมีค่าแล้วหรือจะได้รับค่าใหม่จากแอปหรือจะเหลือเป็นold_or_external_idNULL
  • new_idมักจะมีค่าใหม่ที่สร้างขึ้น แต่ค่าที่จะใช้เฉพาะในกรณีที่คอลัมน์old_or_external_idNULL

ไม่เคยมีช่วงเวลาที่คุณจะต้องค่าทั้งในและold_or_external_id new_idใช่จะมีบางครั้งที่คอลัมน์ทั้งสองมีค่าเนื่องจากnew_idการเป็นIDENTITYแต่new_idค่าเหล่านั้นจะถูกละเว้น อีกครั้งฟิลด์สองฟิลด์นี้ไม่เหมือนกัน แล้วตอนนี้ล่ะ?

ตอนนี้เราสามารถดูว่าทำไมเราต้องการexternal_idในครั้งแรก เมื่อพิจารณาว่ามีความเป็นไปได้ที่จะแทรกลงในIDENTITYคอลัมน์โดยใช้SET IDENTITY_INSERT {table_name} ON;คุณสามารถหนีไปได้โดยไม่ต้องเปลี่ยนแปลงสคีมาเลยและเพียงแค่ปรับเปลี่ยนรหัสแอพของคุณเพื่อห่อINSERTงบ / การดำเนินการSET IDENTITY_INSERT {table_name} ON;และSET IDENTITY_INSERT {table_name} OFF;คำสั่ง จากนั้นคุณต้องกำหนดช่วงเริ่มต้นในการรีเซ็ตIDENTITYคอลัมน์เป็น (สำหรับค่าที่สร้างขึ้นใหม่) เนื่องจากจะต้องมีค่าสูงกว่าค่าที่รหัสแอปจะแทรกเนื่องจากการใส่ค่าที่สูงกว่าจะทำให้ค่าที่สร้างขึ้นอัตโนมัติถัดไปเป็น จะมากกว่าค่า MAX ปัจจุบัน แต่คุณสามารถแทรกค่าที่ต่ำกว่าค่าIDENT_CURRENTได้เสมอ

การรวมคอลัมน์old_or_external_idและnew_idยังไม่เพิ่มโอกาสในการทำงานในสถานการณ์ค่าที่ทับซ้อนกันระหว่างค่าที่สร้างขึ้นอัตโนมัติและค่าที่สร้างโดยแอพเนื่องจากความตั้งใจที่จะมีคอลัมน์ 2 หรือ 3 คอลัมน์คือการรวมเข้ากับค่าคีย์หลัก และนั่นคือค่าที่ไม่ซ้ำกันเสมอ

ในวิธีการนี้คุณเพียงแค่ต้อง:

  • ปล่อยให้ตารางเป็น:

    PkId INT IDENTITY(1,1) PRIMARY KEY

    สิ่งนี้เพิ่ม 0 ไบต์ให้กับแต่ละแถวแทนที่จะเป็น 8 หรือแม้กระทั่ง 12

  • กำหนดช่วงเริ่มต้นสำหรับค่าที่สร้างโดยแอป สิ่งเหล่านี้จะมากกว่าค่า MAX ปัจจุบันในแต่ละตาราง แต่น้อยกว่าสิ่งที่จะกลายเป็นค่าต่ำสุดสำหรับค่าที่สร้างขึ้นอัตโนมัติ
  • กำหนดค่าที่ช่วงที่สร้างโดยอัตโนมัติควรเริ่มต้น ควรมีที่ว่างมากมายระหว่างค่า MAX ปัจจุบันและจำนวนห้องที่จะเติบโตโดยรู้ว่าขีด จำกัด สูงสุดนั้นมีมูลค่าเพียง 2.14 พันล้านเท่านั้น จากนั้นคุณสามารถตั้งค่านี้เมล็ดต่ำสุดใหม่ผ่านDBCC CHECKIDENT
  • ใส่รหัสแอป INSERTs ในSET IDENTITY_INSERT {table_name} ON;และSET IDENTITY_INSERT {table_name} OFF;ข้อความสั่ง

วินาทีส่วน B

รูปแบบเกี่ยวกับวิธีการที่ระบุไว้ข้างต้นโดยตรงคือการให้ค่าการแทรกรหัสแอปเริ่มต้นด้วย -1 และลงไปจากที่นั่น สิ่งนี้ทำให้IDENTITYค่าเป็นเพียงคนเดียวที่ขึ้นไป ประโยชน์ที่นี่คือคุณไม่เพียง แต่ไม่ทำให้คีมายุ่ง แต่คุณไม่ต้องกังวลเกี่ยวกับการทำงานใน ID ที่ทับซ้อนกัน (หากค่าที่สร้างโดยแอพทำงานในช่วงที่สร้างโดยอัตโนมัติใหม่) นี่เป็นเพียงตัวเลือกหากคุณยังไม่ได้ใช้ค่า ID เชิงลบ (และดูเหมือนว่าเป็นเรื่องยากที่ผู้ใช้จะใช้ค่าลบในคอลัมน์ที่สร้างขึ้นอัตโนมัติดังนั้นสิ่งนี้น่าจะเป็นไปได้ในสถานการณ์ส่วนใหญ่)

ในวิธีการนี้คุณเพียงแค่ต้อง:

  • ปล่อยให้ตารางเป็น:

    PkId INT IDENTITY(1,1) PRIMARY KEY

    สิ่งนี้เพิ่ม 0 ไบต์ให้กับแต่ละแถวแทนที่จะเป็น 8 หรือแม้กระทั่ง 12

  • -1ช่วงเริ่มต้นสำหรับค่าแอปที่สร้างขึ้นจะเป็น
  • ใส่รหัสแอป INSERTs ในSET IDENTITY_INSERT {table_name} ON;และSET IDENTITY_INSERT {table_name} OFF;ข้อความสั่ง

ที่นี่คุณยังคงต้องทำIDENTITY_INSERTแต่: คุณไม่ต้องเพิ่มคอลัมน์ใหม่ไม่จำเป็นต้อง "ทำการใหม่" IDENTITYคอลัมน์ใด ๆและไม่มีความเสี่ยงในการทับซ้อนในอนาคต

วินาทีส่วนที่ 3

หนึ่งรูปแบบที่ผ่านมาของวิธีการนี้จะมีการแลกเปลี่ยนอาจจะออกIDENTITYคอลัมน์และแทนที่จะใช้ลำดับ เหตุผลที่จะใช้วิธีการนี้คือการสามารถที่จะมีรหัสแอปแทรกค่าที่: บวกสูงกว่าช่วงที่สร้างขึ้นโดยอัตโนมัติ (ไม่ต่ำกว่า) SET IDENTITY_INSERT ON / OFFและไม่จำเป็นต้องให้

ในวิธีการนี้คุณเพียงแค่ต้อง:

  • สร้างลำดับโดยใช้CREATE SEQUENCE
  • คัดลอกIDENTITYคอลัมน์ไปยังคอลัมน์ใหม่ที่ไม่มีIDENTITYคุณสมบัติ แต่มีDEFAULTข้อ จำกัด โดยใช้ฟังก์ชันNEXT VALUE FOR

    PkId INT PRIMARY KEY CONSTRAINT [DF_TableName_NextID] DEFAULT (NEXT VALUE FOR...)

    สิ่งนี้เพิ่ม 0 ไบต์ให้กับแต่ละแถวแทนที่จะเป็น 8 หรือแม้กระทั่ง 12

  • ช่วงเริ่มต้นสำหรับค่าที่สร้างขึ้นจากแอปจะดีกว่าสิ่งที่คุณคิดว่าค่าที่สร้างขึ้นโดยอัตโนมัติจะเข้าใกล้
  • ใส่รหัสแอป INSERTs ในSET IDENTITY_INSERT {table_name} ON;และSET IDENTITY_INSERT {table_name} OFF;ข้อความสั่ง

อย่างไรก็ตามเนื่องจากความต้องการของรหัสที่มีอย่างใดอย่างหนึ่งSCOPE_IDENTITY()หรือ@@IDENTITYยังคงทำงานอย่างถูกต้องสลับไปยังลำดับที่ไม่ได้เป็นตัวเลือกในขณะที่ปรากฏว่าไม่มีฟังก์ชั่นเหล่านั้นเทียบเท่าสำหรับลำดับ :-( เสียใจ!


ขอบคุณมากสำหรับคำตอบของคุณ คุณเพิ่มคะแนนบางส่วนที่กล่าวถึงที่นี่ภายใน น่าเสียดายที่บางข้อเหล่านี้ใช้ไม่ได้กับเราด้วยเหตุผลสองประการ ฐานข้อมูลของเราค่อนข้างเก่าและค่อนข้างเปราะบางและทำงานภายใต้โหมดความเข้ากันได้ปี 2005 ดังนั้น SEQUENCES จึงไม่ทำงาน การพุชข้อมูลแอพของเราเกิดขึ้นผ่านเครื่องมือโหลดข้อมูลที่รับเร็กคอร์ดใหม่จากคิวนายหน้าบริการและพุชผ่านหลายเธรด IDENTITY_INSERT สามารถใช้งานได้หนึ่งตารางต่อเซสชันเท่านั้นและความคิดในปัจจุบันคือสถาปัตยกรรมของเราไม่สามารถรองรับได้หากไม่มีการเปลี่ยนแปลงที่สำคัญ ฉันกำลังทดสอบข้อเสนอแนะกำปั้นของคุณตอนนี้
Mr Moose

@MoMoose ใช่ฉันได้อัปเดตคำตอบของฉันเพื่อรวมข้อมูลเพิ่มเติมเกี่ยวกับลำดับท้ายที่สุด มันจะไม่ทำงานในสถานการณ์ของคุณต่อไป และฉันสงสัยเกี่ยวกับปัญหาที่เกิดขึ้นพร้อมกันIDENTITY_INSERTแต่ยังไม่ได้ทดสอบ ไม่แน่ใจว่าตัวเลือก # 1 กำลังจะแก้ปัญหาโดยรวมของคุณเป็นเพียงการสังเกตเพื่อลดความซับซ้อนที่ไม่จำเป็น อย่างไรก็ตามหากคุณมีหลายเธรดที่แทรก ID "ภายนอก" ใหม่คุณจะรับประกันได้อย่างไรว่ามันไม่ซ้ำกัน?
โซโลมอน Rutzky

@MrMoose ที่จริงแล้วเกี่ยวกับ " IDENTITY_INSERT สามารถใช้ได้เพียงหนึ่งตารางต่อเซสชัน " สิ่งที่เป็นปัญหาที่นี่? 1) คุณสามารถแทรกเข้าไปในตารางได้ครั้งละหนึ่งโต๊ะเท่านั้นดังนั้นคุณจึงปิดสำหรับ TableA ก่อนที่จะแทรกลงใน TableB และ 2) ฉันเพิ่งทดสอบและตรงกันข้ามกับสิ่งที่ฉันคิดว่าไม่มีปัญหาเกิดขึ้นพร้อมกัน - ฉันสามารถ มีIDENTITY_INSERT ONตารางเดียวกันในสองเซสชันและแทรกลงในทั้งสองโดยไม่มีปัญหา
โซโลมอน Rutzky

1
ตามที่คุณแนะนำการเปลี่ยนแปลง 1 สร้างความแตกต่างเล็กน้อย ID ที่เราจะใช้จะถูกจัดสรรนอกฐานข้อมูลปัจจุบันและใช้เพื่อเชื่อมโยงบันทึก อาจเป็นเพราะความเข้าใจในเซสชันของฉันไม่ถูกต้องดังนั้น IDENTITY_INSERT อาจทำงานได้ ฉันจะใช้เวลาสักครู่เพื่อตรวจสอบแม้ว่าฉันจะไม่สามารถรายงานกลับมาได้ซักพัก ขอบคุณอีกครั้งสำหรับอินพุต มันเป็นที่นิยมมาก
Mr Moose

1
ฉันคิดว่าข้อเสนอแนะของคุณเกี่ยวกับการใช้ IDENTITY_INSERT (ที่มีมูลค่าเมล็ดพันธุ์สูงสำหรับแอปที่มีอยู่) จะใช้งานได้ดี แอรอนเบอร์ทรานด์ที่มีให้คำตอบที่นี่ด้วยตัวอย่างเล็ก ๆ น้อย ๆ ที่ดีในการทดสอบกับเห็นพ้องด้วย เราได้แก้ไขเครื่องมือโหลดข้อมูลของเราเพื่อให้สามารถจัดการตารางที่จำเป็นต้องระบุค่าเอกลักษณ์และเราจะทำการทดสอบเพิ่มเติมในอีกไม่กี่สัปดาห์ข้างหน้า
Mr Moose
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.