การเขียนโครงสร้างธนาคารอย่างง่าย: ฉันจะรักษายอดคงเหลือของฉันให้สอดคล้องกับประวัติการทำธุรกรรมได้อย่างไร


57

ฉันกำลังเขียนคีสำหรับฐานข้อมูลธนาคารที่เรียบง่าย นี่คือคุณสมบัติพื้นฐาน:

  • ฐานข้อมูลจะเก็บธุรกรรมกับผู้ใช้และสกุลเงิน
  • ผู้ใช้ทุกคนมียอดดุลหนึ่งรายการต่อหนึ่งสกุลเงินดังนั้นแต่ละยอดดุลเป็นเพียงผลรวมของธุรกรรมทั้งหมดต่อผู้ใช้และสกุลเงิน
  • ยอดคงเหลือต้องไม่ติดลบ

แอปพลิเคชันธนาคารจะสื่อสารกับฐานข้อมูลผ่านขั้นตอนการจัดเก็บ

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

ตัวเลือกของฉันคือ:

  1. มีbalancesตารางแยกต่างหากและทำสิ่งใดสิ่งหนึ่งต่อไปนี้:

    1. ใช้ธุรกรรมกับทั้งtransactionsและbalancesตาราง ใช้TRANSACTIONตรรกะในเลเยอร์ของโพรซีเดอร์ที่เก็บไว้เพื่อให้แน่ใจว่ายอดคงเหลือและการทำธุรกรรมมีการซิงค์อยู่เสมอ (รองรับโดยแจ็ค )

    2. ใช้ธุรกรรมกับtransactionsตารางและมีทริกเกอร์ที่อัปเดตbalancesตารางให้ฉันด้วยจำนวนธุรกรรม

    3. ใช้ธุรกรรมกับbalancesตารางและมีทริกเกอร์ที่เพิ่มรายการใหม่ในtransactionsตารางให้ฉันด้วยจำนวนธุรกรรม

    ฉันต้องพึ่งพาวิธีการรักษาความปลอดภัยเพื่อให้แน่ใจว่าไม่มีการเปลี่ยนแปลงใด ๆ นอกกระบวนการที่เก็บไว้ มิฉะนั้นตัวอย่างเช่นกระบวนการบางอย่างสามารถแทรกธุรกรรมลงในtransactionsตารางโดยตรงและภายใต้โครงการ1.3ยอดคงเหลือที่เกี่ยวข้องจะไม่ซิงค์กัน

  2. มีbalancesมุมมองที่จัดทำดัชนีที่รวมการทำธุรกรรมอย่างเหมาะสม เครื่องมือเก็บข้อมูลรับประกันยอดคงเหลือเพื่อให้สอดคล้องกับการทำธุรกรรมของพวกเขาดังนั้นฉันไม่จำเป็นต้องพึ่งพาวิธีการรักษาความปลอดภัยเพื่อรับประกันสิ่งนี้ ในทางกลับกันฉันไม่สามารถบังคับยอดคงเหลือให้เป็นค่าที่ไม่เป็นลบได้อีกต่อไปนับตั้งแต่การดู - แม้แต่การดูที่มีการจัดทำดัชนี - ไม่สามารถมีCHECKข้อ จำกัด ได้ (สนับสนุนโดยDenny )

  3. มีเพียงtransactionsตารางแต่มีคอลัมน์เพิ่มเติมเพื่อจัดเก็บยอดคงเหลือที่มีประสิทธิภาพหลังจากทำรายการนั้นแล้ว ดังนั้นบันทึกธุรกรรมล่าสุดสำหรับผู้ใช้และสกุลเงินจึงมียอดดุลปัจจุบัน (แนะนำโดยAndrewด้านล่าง; ตัวแปรที่เสนอโดยgarik )

ครั้งแรกที่ผมจัดการปัญหานี้ผมอ่านเหล่านี้ สอง2การอภิปรายและการตัดสินใจในตัวเลือก สำหรับการอ้างอิงคุณสามารถดูการดำเนินงานที่เปลือยกระดูกของมันนี่

  • คุณออกแบบหรือจัดการฐานข้อมูลเช่นนี้ด้วยโปรไฟล์การโหลดสูงหรือไม่ คุณมีวิธีแก้ไขปัญหานี้อย่างไร

  • คุณคิดว่าฉันได้เลือกการออกแบบที่ถูกต้องหรือไม่? มีอะไรบ้างที่ฉันควรจำไว้?

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

  • หากมุมมองที่จัดทำดัชนีเป็นวิธีที่จะไปฉันจะรับประกันได้อย่างไรว่าไม่มียอดคงเหลือติดลบ?


ธุรกรรมที่เก็บถาวร:

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

ตัวอย่างเช่นรายการธุรกรรมนี้:

user_id    currency_id      amount    is_summary
------------------------------------------------
      3              1       10.60             0
      3              1      -55.00             0
      3              1      -12.12             0

ถูกเก็บถาวรออกไปและแทนที่ด้วยสิ่งนี้:

user_id    currency_id      amount    is_summary
------------------------------------------------
      3              1      -56.52             1

ด้วยวิธีนี้ดุลที่มีธุรกรรมที่เก็บถาวรจะรักษาประวัติธุรกรรมที่สมบูรณ์และสอดคล้องกัน


1
หากคุณเลือกตัวเลือก 2 (ซึ่งฉันคิดว่าสะอาดกว่า) ให้ดูที่pgcon.org/2008/schedule/attachments/วิธีการใช้งาน "มุมมองที่เป็นรูปธรรม" ได้อย่างมีประสิทธิภาพ สำหรับตัวเลือกที่ 1 บทที่ 11 ของคณิตศาสตร์ประยุกต์ประยุกต์ของ Haan และ Koppelaars สำหรับผู้เชี่ยวชาญด้านฐานข้อมูล (ไม่ต้องกังวลเกี่ยวกับชื่อ) จะเป็นประโยชน์ในการทำความเข้าใจวิธีใช้ "ข้อ จำกัด การเปลี่ยนแปลง" ได้อย่างมีประสิทธิภาพ ลิงค์แรกคือ PostgreSQL และลิงค์ที่สองสำหรับ Oracle แต่เทคนิคควรใช้ได้กับระบบฐานข้อมูลที่เหมาะสม
jp

ในทางทฤษฎีคุณต้องการทำ # 3 วิธีที่ถูกต้องในการทำ "ยอดคงเหลือ" คือการกำหนดยอดคงเหลือให้กับแต่ละธุรกรรม ตรวจสอบให้แน่ใจว่าคุณสามารถสั่งซื้อการทำธุรกรรมอย่างแน่นอนทั้งที่มีซีเรียล ID (ที่ต้องการ) หรือการประทับเวลา คุณไม่ควรที่จะ "คำนวณ" ยอดดุลคงเหลือ
pbreitenbach

คำตอบ:


15

ฉันไม่คุ้นเคยกับการบัญชี แต่ฉันแก้ไขปัญหาที่คล้ายกันในสภาพแวดล้อมประเภทสินค้าคงคลัง ฉันเก็บผลรวมสะสมในแถวเดียวกันด้วยธุรกรรม ฉันกำลังใช้ข้อ จำกัด ดังนั้นข้อมูลของฉันจะไม่ผิดแม้ภายใต้ภาวะพร้อมกันสูง ฉันได้เขียนวิธีแก้ปัญหาต่อไปนี้แล้วในปี 2009 :

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

CREATE TABLE Data.Inventory(InventoryID INT NOT NULL IDENTITY,
  ItemID INT NOT NULL,
  ChangeDate DATETIME NOT NULL,
  ChangeQty INT NOT NULL,
  TotalQty INT NOT NULL,
  PreviousChangeDate DATETIME NULL,
  PreviousTotalQty INT NULL,
  CONSTRAINT PK_Inventory PRIMARY KEY(ItemID, ChangeDate),
  CONSTRAINT UNQ_Inventory UNIQUE(ItemID, ChangeDate, TotalQty),
  CONSTRAINT UNQ_Inventory_Previous_Columns 
     UNIQUE(ItemID, PreviousChangeDate, PreviousTotalQty),
  CONSTRAINT FK_Inventory_Self FOREIGN KEY(ItemID, PreviousChangeDate, PreviousTotalQty)
    REFERENCES Data.Inventory(ItemID, ChangeDate, TotalQty),
  CONSTRAINT CHK_Inventory_Valid_TotalQty CHECK(
         TotalQty >= 0 
     AND (TotalQty = COALESCE(PreviousTotalQty, 0) + ChangeQty)
  ),
  CONSTRAINT CHK_Inventory_Valid_Dates_Sequence CHECK(PreviousChangeDate < ChangeDate),
  CONSTRAINT CHK_Inventory_Valid_Previous_Columns CHECK(
        (PreviousChangeDate IS NULL AND PreviousTotalQty IS NULL)
     OR (PreviousChangeDate IS NOT NULL AND PreviousTotalQty IS NOT NULL)
  )
);

-- beginning of inventory for item 1
INSERT INTO Data.Inventory(ItemID,
  ChangeDate,
  ChangeQty,
  TotalQty,
  PreviousChangeDate,
  PreviousTotalQty)
VALUES(1, '20090101', 10, 10, NULL, NULL);

-- cannot begin the inventory for the second time for the same item 1
INSERT INTO Data.Inventory(ItemID,
  ChangeDate,
  ChangeQty,
  TotalQty,
  PreviousChangeDate,
  PreviousTotalQty)
VALUES(1, '20090102', 10, 10, NULL, NULL);


Msg 2627, Level 14, State 1, Line 10

Violation of UNIQUE KEY constraint 'UNQ_Inventory_Previous_Columns'. 
Cannot insert duplicate key in object 'Data.Inventory'.

The statement has been terminated.


-- add more
DECLARE @ChangeQty INT;
SET @ChangeQty = 5;

INSERT INTO Data.Inventory(ItemID,
  ChangeDate,
  ChangeQty,
  TotalQty,
  PreviousChangeDate,
  PreviousTotalQty)

SELECT TOP 1 ItemID, '20090103', @ChangeQty, TotalQty + @ChangeQty, ChangeDate, TotalQty
  FROM Data.Inventory
  WHERE ItemID = 1
  ORDER BY ChangeDate DESC;

SET @ChangeQty = 3;

INSERT INTO Data.Inventory(ItemID,
  ChangeDate,
  ChangeQty,
  TotalQty,
  PreviousChangeDate,
  PreviousTotalQty)

SELECT TOP 1 ItemID, '20090104', @ChangeQty, TotalQty + @ChangeQty, ChangeDate, TotalQty
  FROM Data.Inventory
  WHERE ItemID = 1
  ORDER BY ChangeDate DESC;

SET @ChangeQty = -4;

INSERT INTO Data.Inventory(ItemID,
  ChangeDate,
  ChangeQty,
  TotalQty,
  PreviousChangeDate,
  PreviousTotalQty)

SELECT TOP 1 ItemID, '20090105', @ChangeQty, TotalQty + @ChangeQty, ChangeDate, TotalQty
  FROM Data.Inventory
  WHERE ItemID = 1
  ORDER BY ChangeDate DESC;

-- try to violate chronological order
SET @ChangeQty = 5;

INSERT INTO Data.Inventory(ItemID,
  ChangeDate,
  ChangeQty,
  TotalQty,
  PreviousChangeDate,
  PreviousTotalQty)

SELECT TOP 1 ItemID, '20081231', @ChangeQty, TotalQty + @ChangeQty, ChangeDate, TotalQty
  FROM Data.Inventory
  WHERE ItemID = 1
  ORDER BY ChangeDate DESC;

Msg 547, Level 16, State 0, Line 4

The INSERT statement conflicted with the CHECK constraint 
"CHK_Inventory_Valid_Dates_Sequence". 
The conflict occurred in database "Test", table "Data.Inventory".

The statement has been terminated.

SELECT ChangeDate,
  ChangeQty,
  TotalQty,
  PreviousChangeDate,
  PreviousTotalQty
FROM Data.Inventory ORDER BY ChangeDate;

ChangeDate              ChangeQty   TotalQty    PreviousChangeDate      PreviousTotalQty
----------------------- ----------- ----------- ----------------------- -----
2009-01-01 00:00:00.000 10          10          NULL                    NULL
2009-01-03 00:00:00.000 5           15          2009-01-01 00:00:00.000 10
2009-01-04 00:00:00.000 3           18          2009-01-03 00:00:00.000 15
2009-01-05 00:00:00.000 -4          14          2009-01-04 00:00:00.000 18


-- try to change a single row, all updates must fail
UPDATE Data.Inventory SET ChangeQty = ChangeQty + 2 WHERE InventoryID = 3;
UPDATE Data.Inventory SET TotalQty = TotalQty + 2 WHERE InventoryID = 3;

-- try to delete not the last row, all deletes must fail
DELETE FROM Data.Inventory WHERE InventoryID = 1;
DELETE FROM Data.Inventory WHERE InventoryID = 3;

-- the right way to update
DECLARE @IncreaseQty INT;

SET @IncreaseQty = 2;

UPDATE Data.Inventory 
SET 
     ChangeQty = ChangeQty 
   + CASE 
        WHEN ItemID = 1 AND ChangeDate = '20090103' 
        THEN @IncreaseQty 
        ELSE 0 
     END,
  TotalQty = TotalQty + @IncreaseQty,
  PreviousTotalQty = PreviousTotalQty + 
     CASE 
        WHEN ItemID = 1 AND ChangeDate = '20090103' 
        THEN 0 
        ELSE @IncreaseQty 
     END
WHERE ItemID = 1 AND ChangeDate >= '20090103';

SELECT ChangeDate,
  ChangeQty,
  TotalQty,
  PreviousChangeDate,
  PreviousTotalQty
FROM Data.Inventory ORDER BY ChangeDate;

ChangeDate              ChangeQty   TotalQty    PreviousChangeDate      PreviousTotalQty
----------------------- ----------- ----------- ----------------------- ----------------
2009-01-01 00:00:00.000 10          10          NULL                    NULL
2009-01-03 00:00:00.000 7           17          2009-01-01 00:00:00.000 10
2009-01-04 00:00:00.000 3           20          2009-01-03 00:00:00.000 17
2009-01-05 00:00:00.000 -4          16          2009-01-04 00:00:00.000 20

14

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

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


1
ฉันสามารถดูได้ว่าทำไมข้อ จำกัด ของยอดคงเหลือควรเป็นกฎธุรกิจ ฐานข้อมูลเป็นเพียงการให้บริการธุรกรรมและขึ้นอยู่กับผู้ใช้ในการตัดสินใจว่าจะทำอย่างไรกับมัน
Nick Chammas

คุณคิดอย่างไรเกี่ยวกับความคิดเห็นของแจ็คที่ใช้ทั้งสองตารางทำให้ผู้พัฒนามีความยืดหยุ่นในการเปลี่ยนแปลงหรือใช้ตรรกะทางธุรกิจมากขึ้น นอกจากนี้คุณมีประสบการณ์ตรงกับมุมมองที่จัดทำดัชนีซึ่งตรวจสอบหรือท้าทายข้อกังวลเหล่านี้หรือไม่?
Nick Chammas

1
ฉันจะไม่พูดว่าการมีสองตารางให้ความยืดหยุ่นคือการใช้ตรรกะทางธุรกิจ มันทำให้คุณมีความยืดหยุ่นมากขึ้นในการจัดเก็บข้อมูล อย่างไรก็ตามในฐานะธนาคาร (อย่างน้อยในสหรัฐอเมริกา) คุณมีกฎหมายที่บอกว่าต้องเก็บข้อมูลไว้เท่าไหร่ คุณจะต้องทดสอบว่าประสิทธิภาพดูอย่างไรกับมุมมองด้านบนรวมทั้งคำนึงถึงว่าหากคุณมีมุมมองที่จัดทำดัชนีคุณจะไม่สามารถเปลี่ยนสคีมาของตารางต้นแบบได้ เป็นอีกเรื่องที่ต้องคิด
mrdenny

รายการทั้งหมดที่กล่าวถึงในบทความเป็นข้อกังวลที่ถูกต้องเกี่ยวกับสิ่งที่เมื่อใช้มุมมองที่จัดทำดัชนี
mrdenny

1
เพื่อชี้แจง: IMO API การทำธุรกรรมให้ความยืดหยุ่นมากขึ้นในการใช้ตรรกะทางธุรกิจ (ไม่มีสองตาราง) ในกรณีนี้ฉันต้องการสองตาราง (อย่างน้อยก็ให้ข้อมูลที่เรามี) เนื่องจากการแลกเปลี่ยนที่เสนอด้วยวิธีการจัดทำดัชนี (เช่นไม่สามารถใช้ DRI เพื่อบังคับยอดคงเหลือ> 0 ธุรกิจ กฎ)
แจ็คดักลาส

13

แนวทางที่แตกต่างกันเล็กน้อย (คล้ายกับตัวเลือกที่2ของคุณ) ที่ต้องพิจารณาคือมีเพียงตารางธุรกรรมที่มีคำจำกัดความของ:

CREATE TABLE Transaction (
      UserID              INT
    , CurrencyID          INT 
    , TransactionDate     DATETIME  
    , OpeningBalance      MONEY
    , TransactionAmount   MONEY
);

คุณอาจต้องการรหัสธุรกรรม / คำสั่งซื้อเพื่อให้คุณสามารถจัดการธุรกรรมสองรายการในวันเดียวกันและปรับปรุงแบบสอบถามสืบค้นของคุณ

เพื่อให้ได้ยอดเงินปัจจุบันทั้งหมดที่คุณต้องได้รับคือระเบียนสุดท้าย

วิธีการรับบันทึกล่าสุด :

/* For a single User/Currency */
Select TOP 1 *
FROM dbo.Transaction
WHERE UserID = 3 and CurrencyID = 1
ORDER By TransactionDate desc

/* For multiple records ie: to put into a view (which you might want to index) */
SELECT
    C.*
FROM
    (SELECT 
        *, 
        ROW_NUMBER() OVER (
           PARTITION BY UserID, CurrencyID 
           ORDER BY TransactionDate DESC
        ) AS rnBalance 
    FROM Transaction) C
WHERE
    C.rnBalance = 1
ORDER BY
    C.UserID, C.CurrencyID

จุดด้อย:

  • เมื่อแทรกการทำธุรกรรมที่ไม่เป็นไปตามลำดับ (เช่น: เพื่อแก้ไขปัญหา / ยอดเงินเริ่มต้นไม่ถูกต้อง) คุณอาจจำเป็นต้องปรับปรุงการปรับปรุงสำหรับธุรกรรมที่ตามมาทั้งหมด
  • การทำธุรกรรมสำหรับผู้ใช้ / สกุลเงินจะต้องได้รับการต่อเนื่องเพื่อรักษาสมดุลที่ถูกต้อง

    -- Example of getting the current balance and locking the 
    -- last record for that User/Currency.
    -- This lock will be freed after the Stored Procedure completes.
    SELECT TOP 1 @OldBalance = OpeningBalance + TransactionAmount  
    FROM dbo.Transaction with (rowlock, xlock)   
    WHERE UserID = 3 and CurrencyID = 1  
    ORDER By TransactionDate DESC;
    

ข้อดี:

  • คุณไม่จำเป็นต้องดูแลรักษาสองตารางแยกกัน ...
  • คุณสามารถตรวจสอบยอดคงเหลือได้อย่างง่ายดายและเมื่อยอดเงินคงเหลือไม่ตรงกันคุณสามารถระบุได้อย่างชัดเจนว่าเมื่อใดที่ยอดขายไม่ผ่านเนื่องจากประวัติการทำธุรกรรมกลายเป็นการจัดทำเอกสารด้วยตนเอง

แก้ไข: คำค้นหาตัวอย่างบางส่วนเกี่ยวกับการเรียกยอดเงินปัจจุบันและไฮไลต์การต่อต้าน (ขอบคุณ @Jack Douglas)


3
SELECT TOP (1) ... ORDER BY TransactionDate DESCจะยุ่งยากมากในการดำเนินการในลักษณะที่ SQL Server ไม่ต่อเนื่องสแกนตารางการทำธุรกรรม Alex Kuznetsov โพสต์วิธีแก้ปัญหาที่นี่เพื่อแก้ไขปัญหาการออกแบบที่คล้ายกันที่สมบูรณ์แบบเติมเต็มคำตอบนี้
Nick Chammas

2
+1 ฉันใช้วิธีการที่คล้ายกัน BTW เราต้องระวังให้มากและตรวจสอบให้แน่ใจว่าโค้ดของเราทำงานอย่างถูกต้องภายใต้ปริมาณงานที่เกิดขึ้นพร้อมกัน
AK

12

หลังจากอ่านการอภิปรายสองครั้งนี้ฉันตัดสินใจเลือก 2

หลังจากที่ได้อ่านการสนทนาเหล่านั้นด้วยฉันไม่แน่ใจว่าทำไมคุณถึงตัดสินใจใช้DRI solution เหนือกว่าตัวเลือกอื่น ๆ

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

ชนิดของการแก้ปัญหานี้มีประโยชน์ในทางปฏิบัติอันยิ่งใหญ่ถ้าคุณมีความหรูหราของการ จำกัดทุกการเข้าถึงข้อมูลผ่าน API การทำธุรกรรมของคุณ คุณจะสูญเสียผลประโยชน์ที่สำคัญมากของ DRI ซึ่งเป็นที่สมบูรณ์มีการประกันโดยฐานข้อมูล แต่ในรูปแบบของความซับซ้อนใด ๆ เพียงพอที่จะมีบางกฎเกณฑ์ทางธุรกิจที่ไม่สามารถบังคับใช้โดย DRI

ฉันแนะนำให้ใช้ DRI หากเป็นไปได้ในการบังคับใช้กฎเกณฑ์ทางธุรกิจโดยไม่ทำให้โมเดลของคุณมากเกินไปเพื่อให้เป็นไปได้:

แม้ว่าฉันจะเก็บถาวรธุรกรรม (เช่นโดยการย้ายไปที่อื่นและแทนที่ด้วยธุรกรรมสรุป)

ทันทีที่คุณเริ่มพิจารณาสร้างมลพิษแบบจำลองของคุณเช่นนี้ฉันคิดว่าคุณกำลังเข้าสู่พื้นที่ที่ประโยชน์ของ DRI นั้นเกินดุลจากปัญหาที่คุณแนะนำ พิจารณาเช่นว่าข้อผิดพลาดในขั้นตอนการจัดเก็บข้อมูลของคุณอาจทำให้เกิดในทฤษฎีกฎทองของคุณ (ที่ยอดคงเหลือเสมอเท่ากับผลรวมของการทำธุรกรรม) เพื่อทำลายอย่างเงียบ ๆ ด้วยโซลูชั่น DRI

นี่คือบทสรุปของข้อดีของวิธีการทำธุรกรรมตามที่เห็น:

  • เราควรจะทำสิ่งนี้ต่อไปถ้าเป็นไปได้ ไม่ว่าคุณจะเลือกวิธีการแก้ไขปัญหาใดปัญหานี้จะให้ความยืดหยุ่นในการออกแบบมากขึ้นและควบคุมข้อมูลของคุณ การเข้าถึงทั้งหมดจะกลายเป็น "ธุรกรรม" ในแง่ของตรรกะทางธุรกิจแทนที่จะเป็นเพียงแค่ในแง่ของตรรกะฐานข้อมูล
  • คุณสามารถทำให้แบบจำลองของคุณเรียบร้อย
  • คุณสามารถ "บังคับใช้" ขอบเขตและความซับซ้อนของกฎเกณฑ์ทางธุรกิจที่กว้างขึ้น (สังเกตว่าแนวคิดของ "บังคับใช้" นั้นเป็นเรื่องที่หลวมกว่า DRI)
  • คุณยังคงสามารถใช้ DRI ในทุกที่ที่ใช้งานได้จริงเพื่อให้แบบจำลองมีความสมบูรณ์ที่แข็งแกร่งยิ่งขึ้น - และสิ่งนี้สามารถทำหน้าที่ตรวจสอบตรรกะการทำธุรกรรมของคุณ
  • ปัญหาด้านประสิทธิภาพส่วนใหญ่ที่ทำให้คุณหนักใจจะละลายไป
  • การแนะนำข้อกำหนดใหม่นั้นง่ายกว่ามากตัวอย่างเช่นกฎที่ซับซ้อนสำหรับการทำธุรกรรมที่ขัดแย้งอาจบังคับให้คุณต้องออกห่างจากแนวทาง DRI ที่บริสุทธิ์ไกลออกไปทางเส้นหมายถึงความพยายามที่สูญเปล่า
  • การแบ่งหรือการเก็บถาวรของข้อมูลในอดีตจะมีความเสี่ยงและความเจ็บปวดน้อยลง

--edit

หากต้องการอนุญาตการเก็บถาวรโดยไม่เพิ่มความซับซ้อนหรือความเสี่ยงคุณสามารถเลือกที่จะเก็บแถวสรุปไว้ในตารางสรุปแยกต่างหากซึ่งสร้างขึ้นอย่างต่อเนื่อง (ยืมจาก @Andrew และ @Garik)

ตัวอย่างเช่นหากสรุปเป็นรายเดือน:

  • ทุกครั้งที่มีธุรกรรม (ผ่าน API ของคุณ) จะมีการอัปเดตหรือแทรกที่สอดคล้องกันในตารางสรุป
  • ตารางสรุปจะไม่ถูกเก็บถาวร แต่การทำธุรกรรมการเก็บถาวรจะกลายเป็นเรื่องง่ายที่จะลบ (หรือวางพาร์ทิชัน?)
  • แต่ละแถวในตารางสรุปประกอบด้วย 'ยอดคงเหลือต้นงวด' และ 'จำนวน'
  • ตรวจสอบข้อ จำกัด เช่น 'ยอดคงเหลือต้นงวด' + 'จำนวน'> 0 และ 'ยอดคงเหลือเปิด'> 0 สามารถนำไปใช้กับตารางสรุป
  • สามารถแทรกแถวสรุปในชุดรายเดือนเพื่อให้การล็อคแถวสรุปล่าสุดง่ายขึ้น (จะมีแถวสำหรับเดือนปัจจุบันเสมอ)

เกี่ยวกับการแก้ไขของคุณ: ดังนั้นคุณเสนอให้มีตารางสรุปนี้อยู่ข้างๆตารางยอดคงเหลือหลัก? ตารางยอดคงเหลือนั้นกลายเป็นตารางสรุปที่เพิ่งมีระเบียนสำหรับเดือนปัจจุบันได้อย่างมีประสิทธิภาพหรือไม่ (เนื่องจากทั้งสองจะเก็บข้อมูลชนิดเดียวกัน) หากฉันเข้าใจถูกต้องแล้วทำไมไม่เปลี่ยนตารางยอดคงเหลือเป็นพาร์ติชั่นที่เหมาะสมในตารางสรุป?
Nick Chammas

ขออภัยคุณไม่ถูกต้อง - ฉันหมายถึงการจ่ายเงินด้วยตารางยอดคงเหลือเนื่องจากจะเป็นการค้นหาที่สำคัญในตารางสรุปเพื่อรับยอดเงินปัจจุบันเสมอ (ไม่เป็นความจริงกับข้อเสนอแนะของ Andrews AFAIK) ข้อดีคือการคำนวณยอดคงเหลือในครั้งก่อนจะง่ายขึ้นและมีหลักฐานการตรวจสอบที่ชัดเจนสำหรับยอดคงเหลือหากพวกเขาทำผิด
แจ็คดักลาส

6

กรงขัง

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

 id   user_id    currency_id      amount    is_summary (or record_type)
----------------------------------------------------
  1       3              1       10.60             0
  2       3              1       10.60             1    -- summary after transaction 1
  3       3              1      -55.00             0
  4       3              1      -44.40             1    -- summary after transactions 1 and 3
  5       3              1      -12.12             0
  6       3              1      -56.52             1    -- summary after transactions 1, 3 and 5 

ตัวแปรที่ดีกว่าคือการลดจำนวนของบันทึกสรุป เราสามารถมียอดคงเหลือหนึ่งบันทึกในตอนท้าย (และ / หรือเริ่มต้น) ของวัน ดังที่คุณทราบว่าทุกธนาคารoperational dayต้องเปิดและปิดมากกว่าเพื่อดำเนินการสรุปบางอย่างสำหรับวันนี้ มันช่วยให้เราสามารถคำนวณดอกเบี้ยได้ง่าย ๆโดยใช้บันทึกยอดคงเหลือทุกวันตัวอย่างเช่น

user_id    currency_id      amount    is_summary    oper_date
--------------------------------------------------------------
      3              1       10.60             0    01/01/2011 
      3              1      -55.00             0    01/01/2011
      3              1      -44.40             1    01/01/2011 -- summary at the end of day (01/01/2011)
      3              1      -12.12             0    01/02/2011
      3              1      -56.52             1    01/02/2011 -- summary at the end of day (01/02/2011)

โชค.


4

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

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

การเก็บถาวรจะไม่ส่งผลกระทบต่อวิธีการนี้ คุณสามารถมีตารางสรุปรายสัปดาห์รายเดือนรายปีและหากจำเป็นสำหรับสิ่งต่าง ๆ เช่นรายงาน


3

ในออราเคิลคุณสามารถทำได้โดยใช้เพียงตารางธุรกรรมที่มีมุมมอง Materialized ที่รีเฟรชได้อย่างรวดเร็วซึ่งจะรวมการสร้างยอดคงเหลือ คุณกำหนดทริกเกอร์ในมุมมองวัสดุ หากมีการกำหนดมุมมอง Materialized ด้วย 'ON COMMIT' จะป้องกันการเพิ่ม / แก้ไขข้อมูลในตารางฐานได้อย่างมีประสิทธิภาพ ทริกเกอร์ตรวจพบข้อมูลที่ถูกต้อง [ใน] และทำให้เกิดข้อยกเว้นซึ่งจะย้อนกลับการทำธุรกรรม ตัวอย่างที่ดีอยู่ที่นี่http://www.sqlsnippets.com/en/topic-12896.html

ฉันไม่ทราบ sqlserver แต่อาจมีตัวเลือกที่คล้ายกัน?


2
Materialized Views ใน Oracle นั้นคล้ายคลึงกับ SQL Server "viewed indexed" แต่มันจะทำการรีเฟรชโดยอัตโนมัติแทนที่จะทำการจัดการอย่างชัดเจนเช่นพฤติกรรม 'ON COMMIT' ของออราเคิล ดูsocial.msdn.microsoft.com/Forums/fi-FI/transactsql/thread/…และ techembassy.blogspot.com/2007/01/…
GregW
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.