Tôi nghĩ rằng điều này có thể được thực hiện bằng cách sử dụng một bảng đôi nhỏ lạ mắt và một số ràng buộc.
Hãy bắt đầu bằng một số cấu trúc (không được chuẩn hóa hoàn toàn):
/* Everything goes to one schema... */
CREATE SCHEMA bookings ;
SET search_path = bookings ;
/* A table for theatre sessions (or events, or ...) */
CREATE TABLE sessions
(
session_id integer /* serial */ PRIMARY KEY,
session_theater TEXT NOT NULL, /* Should be normalized */
session_timestamp TIMESTAMP WITH TIME ZONE NOT NULL,
performance_name TEXT, /* Should be normalized */
UNIQUE (session_theater, session_timestamp) /* Alternate natural key */
) ;
/* And one for bookings */
CREATE TABLE bookings
(
session_id INTEGER NOT NULL REFERENCES sessions (session_id),
seat_number INTEGER NOT NULL /* REFERENCES ... */,
booker TEXT NULL,
PRIMARY KEY (session_id, seat_number),
UNIQUE (session_id, seat_number, booker) /* Needed redundance */
) ;
Việc đặt bàn, thay vì có một is_booked
cột, đã có một booker
cột. Nếu nó là null, chỗ ngồi không được đặt, nếu không đây là tên (id) của người đặt sách.
Chúng tôi thêm một số dữ liệu ví dụ ...
-- Sample data
INSERT INTO sessions
(session_id, session_theater, session_timestamp, performance_name)
VALUES
(1, 'Her Majesty''s Theatre',
'2017-01-06 19:30 Europe/London', 'The Phantom of the Opera'),
(2, 'Her Majesty''s Theatre',
'2017-01-07 14:30 Europe/London', 'The Phantom of the Opera'),
(3, 'Her Majesty''s Theatre',
'2017-01-07 19:30 Europe/London', 'The Phantom of the Opera') ;
-- ALl sessions have 100 free seats
INSERT INTO bookings (session_id, seat_number)
SELECT
session_id, seat_number
FROM
generate_series(1, 3) AS x(session_id),
generate_series(1, 100) AS y(seat_number) ;
Chúng tôi tạo một bảng thứ hai để đặt chỗ, với một hạn chế:
CREATE TABLE bookings_with_bookers
(
session_id INTEGER NOT NULL,
seat_number INTEGER NOT NULL,
booker TEXT NOT NULL,
PRIMARY KEY (session_id, seat_number)
) ;
-- Restraint bookings_with_bookers: they must match bookings
ALTER TABLE bookings_with_bookers
ADD FOREIGN KEY (session_id, seat_number, booker)
REFERENCES bookings.bookings (session_id, seat_number, booker) MATCH FULL
ON UPDATE RESTRICT ON DELETE RESTRICT
DEFERRABLE INITIALLY DEFERRED;
Bảng thứ hai này sẽ chứa một BẢN SAO của các bộ dữ liệu (session_id, seat_number, booker), với một FOREIGN KEY
ràng buộc; điều đó sẽ không cho phép các đặt phòng ban đầu được CẬP NHẬT bởi một nhiệm vụ khác. [Giả sử rằng không bao giờ có hai nhiệm vụ xử lý cùng một booker ; nếu đó là trường hợp, một task_id
cột nhất định nên được thêm vào.]
Bất cứ khi nào chúng tôi cần thực hiện đặt phòng, chuỗi các bước tiếp theo trong chức năng sau đây sẽ hiển thị cách:
CREATE or REPLACE FUNCTION book_session
(IN _booker text, IN _session_id integer, IN _number_of_seats integer)
RETURNS integer /* number of seats really booked */ AS
$BODY$
DECLARE
number_really_booked INTEGER ;
BEGIN
-- Choose a random sample of seats, assign them to the booker.
-- Take a list of free seats
WITH free_seats AS
(
SELECT
b.seat_number
FROM
bookings.bookings b
WHERE
b.session_id = _session_id
AND b.booker IS NULL
ORDER BY
random() /* In practice, you'd never do it */
LIMIT
_number_of_seats
FOR UPDATE /* We want to update those rows, and book them */
)
-- Update the 'bookings' table to have our _booker set in.
, update_bookings AS
(
UPDATE
bookings.bookings b
SET
booker = _booker
FROM
free_seats
WHERE
b.session_id = _session_id AND
b.seat_number = free_seats.seat_number
RETURNING
b.session_id, b.seat_number, b.booker
)
-- Insert all this information in our second table,
-- that acts as a 'lock'
, insert_into_bookings_with_bookers AS
(
INSERT INTO
bookings.bookings_with_bookers (session_id, seat_number, booker)
SELECT
update_bookings.session_id,
update_bookings.seat_number,
update_bookings.booker
FROM
update_bookings
RETURNING
bookings.bookings_with_bookers.seat_number
)
-- Count real number of seats booked, and return it
SELECT
count(seat_number)
INTO
number_really_booked
FROM
insert_into_bookings_with_bookers ;
RETURN number_really_booked ;
END ;
$BODY$
LANGUAGE plpgsql VOLATILE NOT LEAKPROOF STRICT
COST 10000 ;
Để thực sự đặt chỗ, chương trình của bạn nên cố gắng thực hiện một cái gì đó như:
-- Whenever we wich to book 37 seats for session 2...
BEGIN TRANSACTION ;
SELECT
book_session('Andrew the Theater-goer', 2, 37) ;
/* Three things can happen:
- The select returns the wished number of seats
=> COMMIT
This can cause an EXCEPTION, and a need for (implicit)
ROLLBACK which should be handled and the process
retried a number of times
if no exception => the process is finished, you have your booking
- The select returns less than the wished number of seats
=> ROLLBACK and RETRY
we don't have enough seats, or some rows changed during function
execution
- (There can be a deadlock condition... that should be handled)
*/
COMMIT /* or ROLLBACK */ TRANSACTION ;
Điều này phụ thuộc vào hai sự kiện 1. FOREIGN KEY
Ràng buộc sẽ không cho phép dữ liệu bị phá vỡ . 2. Chúng tôi CẬP NHẬT bảng đặt chỗ, nhưng chỉ CHERTN (và không bao giờ CẬP NHẬT ) trên bảng đặt phòng_with_bookers một (bảng thứ hai).
Nó không cần SERIALIZABLE
mức cô lập, điều này sẽ đơn giản hóa rất nhiều logic. Tuy nhiên, trong thực tế, các khóa chết sẽ được dự kiến và chương trình tương tác với cơ sở dữ liệu nên được thiết kế để xử lý chúng.