ช่องว่างที่ไม่คาดคิดในคอลัมน์ IDENTITY


18

ฉันพยายามสร้างหมายเลขใบสั่งซื้อที่ไม่ซ้ำกันซึ่งเริ่มต้นที่ 1 และเพิ่มขึ้นทีละ 1 ฉันมีตาราง PONumber ที่สร้างโดยใช้สคริปต์นี้:

CREATE TABLE [dbo].[PONumbers]
(
  [PONumberPK] [int] IDENTITY(1,1) NOT NULL,
  [NewPONo] [bit] NOT NULL,
  [DateInserted] [datetime] NOT NULL DEFAULT GETDATE(),
  CONSTRAINT [PONumbersPK] PRIMARY KEY CLUSTERED ([PONumberPK] ASC)    
);

และขั้นตอนการจัดเก็บที่สร้างขึ้นโดยใช้สคริปต์นี้:

CREATE PROCEDURE [dbo].[GetPONumber] 
AS
BEGIN
    SET NOCOUNT ON;

    INSERT INTO [dbo].[PONumbers]([NewPONo]) VALUES(1);
    SELECT SCOPE_IDENTITY() AS PONumber;
END

ในช่วงเวลาแห่งการสร้างสรรค์สิ่งนี้ใช้ได้ดี เมื่อกระบวนงานที่เก็บไว้ทำงานนั้นจะเริ่มต้นที่หมายเลขที่ต้องการและเพิ่มขึ้นทีละ 1

สิ่งที่แปลกคือถ้าฉันปิดหรือไฮเบอร์เนตคอมพิวเตอร์ของฉันในครั้งต่อไปที่โพรซีเดอร์รันลำดับนั้นจะสูงขึ้นเกือบ 1,000

ดูผลลัพธ์ด้านล่าง:

หมายเลข PO

คุณจะเห็นว่าจำนวนนั้นเพิ่มขึ้นจาก 8 เป็น 1002!

  • ทำไมสิ่งนี้จึงเกิดขึ้น
  • ฉันจะแน่ใจได้อย่างไรว่าตัวเลขจะไม่ถูกข้ามเช่นนั้น
  • ทั้งหมดที่ฉันต้องการสำหรับ SQL เพื่อสร้างตัวเลขที่:
    • a) รับประกันความแปลกใหม่
    • b) เพิ่มขึ้นตามจำนวนที่ต้องการ

ฉันยอมรับว่าฉันไม่ใช่ผู้เชี่ยวชาญ SQL ฉันเข้าใจผิดว่า SCOPE_IDENTITY () ทำอะไร ฉันควรใช้วิธีการอื่นหรือไม่? ฉันค้นหาลำดับใน SQL 2012+ แต่ Microsoft แจ้งว่าไม่รับประกันว่าจะไม่ซ้ำกันตามค่าเริ่มต้น

คำตอบ:


25

นี่เป็นปัญหาที่ทราบและคาดหมาย - คอลัมน์ IDENTITY ได้รับการจัดการโดย SQL Server ที่มีการเปลี่ยนแปลงใน SQL Server 2012 ( พื้นหลังบางส่วน ); โดยค่าเริ่มต้นแล้วแคชจะมีค่า 1,000 ค่าและหากคุณรีสตาร์ทเซิร์ฟเวอร์ SQL รีบูตเซิร์ฟเวอร์ล้มเหลว ฯลฯ จะต้องทิ้งค่า 1,000 ค่าเหล่านั้นเพราะจะไม่มีวิธีที่เชื่อถือได้ที่จะรู้ว่ามีกี่คนในนั้น ออก. นี้เป็นเอกสารที่นี่ มีการตั้งค่าสถานะการสืบค้นกลับที่เปลี่ยนแปลงลักษณะการทำงานดังกล่าวว่าการกำหนด IDENTITY ทุกครั้งจะถูกบันทึก * ป้องกันช่องว่างที่เฉพาะเจาะจงเหล่านั้น อย่างไรก็ตามสิ่งสำคัญคือต้องทราบว่าสิ่งนี้อาจมีค่าใช้จ่ายค่อนข้างสูงในแง่ของประสิทธิภาพดังนั้นฉันจึงไม่ต้องพูดถึงค่าสถานะการติดตามเฉพาะที่นี่

* (โดยส่วนตัวแล้วฉันคิดว่านี่เป็นปัญหาทางเทคนิคที่สามารถแก้ไขได้แตกต่างกัน แต่เนื่องจากฉันไม่ได้เขียนโปรแกรมฉันไม่สามารถเปลี่ยนแปลงได้)

เพื่อให้ชัดเจนเกี่ยวกับการทำงานของตัวตนและลำดับเหตุการณ์:

  • ไม่รับประกันว่าจะไม่ซ้ำกัน (คุณต้องบังคับใช้ที่ระดับตารางโดยใช้คีย์หลักหรือข้อ จำกัด ที่ไม่ซ้ำกัน)
  • ไม่รับประกันว่าจะไม่มีช่องว่าง (เช่นการย้อนกลับหรือลบตัวอย่างเช่นจะสร้างช่องว่างปัญหาเฉพาะนี้แม้จะมี)

เอกลักษณ์เป็นเรื่องง่ายในการบังคับใช้ หลีกเลี่ยงช่องว่างไม่ได้ คุณจำเป็นต้องกำหนดความสำคัญของการหลีกเลี่ยงช่องว่างเหล่านี้ (ในทางทฤษฎีคุณไม่ควรสนใจช่องว่างเลยเนื่องจากค่าประจำตัว / ลำดับลำดับควรเป็นกุญแจตัวแทนที่ไม่มีความหมาย) ถ้ามันเป็นสิ่งสำคัญมากแล้วคุณไม่ควรจะใช้การดำเนินการอย่างใดอย่างหนึ่ง แต่ม้วนกำเนิดลำดับ serializable ของคุณเอง (ดูความคิดบางอย่างที่นี่ , ที่นี่และที่นี่ ) - เพียงแค่ทราบว่ามันจะฆ่าเห็นพ้องด้วย

พื้นหลังมากมายใน "ปัญหา" นี้:


คำตอบนี้ (ยกเว้นส่วน "การตั้งค่าสถานะการสืบค้นกลับ") ยังใช้กับฐานข้อมูล SQL อื่น ๆ ส่วนใหญ่ (คนที่มีลำดับอยู่แล้ว)
mustaccio

ขอบคุณสำหรับคำตอบ. เอกลักษณ์เป็นข้อกำหนดที่สำคัญที่สุดเพียงข้อเดียว ช่องว่างไม่ใช่เรื่องใหญ่ตราบใดที่ยังไม่ใหญ่ เช่นไปจาก 1 ถึง 4 จะยอมรับได้ แต่จาก 4 ถึง 1003 จะไม่เป็นเช่นนั้น
Ege Ersoz

1
เวอร์ชั่นย่อ: ค่า ID จะใช้เป็นหมายเลขใบสั่งซื้อ ลูกค้าเรียกใช้รายงานรายเดือนและต้องการที่จะบอกได้อย่างรวดเร็วว่ามีการส่งใบสั่งซื้อจำนวนเท่าใดในเดือนนั้นเพียงแค่ดูที่หมายเลข PO ดังนั้นเราจึงไม่สามารถเพิ่มได้อีก ~ 1,000 (มีการบำรุงรักษารายสัปดาห์ที่เซิร์ฟเวอร์ทั้งหมดรวมถึงเซิร์ฟเวอร์ DB ถูกรีสตาร์ท)
Ege Ersoz

3
ทำไมคุณไม่ให้รายงานที่ง่ายมากที่เพิ่งใช้ ROW_NUMBER () มากกว่า (ส่วนแบ่งตามเดือนเรียงตาม ID) อีกครั้งหมายเลข ID ควรไร้ความหมายนั่นเป็นวิธีที่แย่มากในการคัดลอกจำนวนคำสั่งซื้อ ถ้าคุณมีข้อบกพร่องในรหัสของคุณที่ลบ 1,000 แถวหรือย้อนกลับ 275 ธุรกรรมหรือ 500 คำสั่งถูกยกเลิกอย่างถูกกฎหมาย?
Aaron Bertrand

1
@Ege: "... บอกจำนวน ... เพียงแค่ดูหมายเลข PO" ผู้ใช้ของคุณจะผิดหวัง ค่าตัวตนจะไม่ทำงานอย่างนั้นและคุณไม่ควร (หรือพวกเขา) ทำสมมติฐานดังกล่าว ไม่ซ้ำ? ใช่. ติดต่อกัน? ไม่วิธีที่ถูกต้องในการนับ PO ที่ส่งระหว่างเดือนคือ ... เพื่อนับจำนวน PO ที่เพิ่มขึ้นในเดือนนั้นโดยยึดตามฟิลด์ [วันที่ที่ไม่สามารถเปลี่ยนแปลงได้] ในแต่ละเรคคอร์ด
Phill W.

-4

นี่เป็นปัญหาของ SQL Server สิ่งที่คุณทำได้คือส่งใหม่คอลัมน์

ลบรายการที่มี ID คอลัมน์ไม่ถูกต้อง Reseed identity column จากนั้นรายการถัดไปจะมี ID ที่ถูกต้อง

Reseed Identity โดยใช้คำสั่ง sql ต่อไปนี้: DBCC CHECKIDENT ('YOUR_TABLE_NAME', RESEED, 9)- 9 เป็นรหัสที่ถูกต้องครั้งสุดท้าย


1
คุณหมายความว่าอย่างไรกับ "ลบรายการ"?
ypercubeᵀᴹ

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