Tôi đang triển khai một trình đọc RSS dựa trên web đơn giản sử dụng python (không thực sự có liên quan) và Postgresql (9.2 nếu có liên quan). Giản đồ cơ sở dữ liệu như sau (dựa trên định dạng RSS):Chèn hàng nếu không tồn tại dẫn đến điều kiện chủng tộc?
CREATE TABLE feed_channel
(
id SERIAL PRIMARY KEY,
name TEXT,
link TEXT NOT NULL,
title TEXT
);
CREATE TABLE feed_content
(
id SERIAL PRIMARY KEY,
channel INTEGER REFERENCES feed_channel(id) ON DELETE CASCADE ON UPDATE CASCADE,
guid TEXT UNIQUE NOT NULL,
title TEXT,
link TEXT,
description TEXT,
pubdate TIMESTAMP
);
Khi tôi tạo ra một kênh mới (và cũng có thể truy vấn để biết thức ăn chăn nuôi được cập nhật) Tôi yêu cầu thức ăn chăn nuôi, chèn dữ liệu của nó vào bảng feed_channel, lựa chọn ID mới được chèn - hoặc hiện tại để tránh trùng lặp - và sau đó thêm dữ liệu nguồn cấp dữ liệu vào bảng feed_content. Một kịch bản điển hình sẽ là:
- Query url thức ăn chăn nuôi, thức ăn chăn nuôi tiêu đề lấy và tất cả các nội dung hiện tại
- Chèn tiêu đề thức ăn vào feed_channel nếu không tồn tại ... nếu đã tồn tại, lấy ID hiện
- Đối với mỗi mục nguồn cấp dữ liệu, hãy chèn vào bảng feed_content có tham chiếu đến ID kênh được lưu trữ
Đây là vấn đề "chèn nếu chưa tồn tại nhưng trả về ID có liên quan". Để giải quyết vấn đề này, tôi đã thực hiện quy trình được lưu sau đây:
CREATE OR REPLACE FUNCTION channel_insert(
p_link feed_channel.link%TYPE,
p_title feed_channel.title%TYPE
) RETURNS feed_channel.id%TYPE AS $$
DECLARE
v_id feed_channel.id%TYPE;
BEGIN
SELECT id
INTO v_id
FROM feed_channel
WHERE link=p_link AND title=p_title
LIMIT 1;
IF v_id IS NULL THEN
INSERT INTO feed_channel(name,link,title)
VALUES (DEFAULT,p_link,p_title)
RETURNING id INTO v_id;
END IF;
RETURN v_id;
END;
$$ LANGUAGE plpgsql;
Điều này sau đó được gọi là "chọn channel_insert (liên kết, tiêu đề);" từ ứng dụng của tôi để chèn nếu chưa tồn tại và sau đó trả lại ID của hàng liên quan bất kể nó được chèn hay chỉ tìm thấy (bước 2 trong danh sách ở trên).
Công trình này tuyệt vời!
Tuy nhiên, gần đây tôi đã bắt đầu tự hỏi điều gì sẽ xảy ra nếu quy trình này được thực hiện hai lần cùng một lúc với cùng một đối số. Cho phép giả định như sau:
- User 1 nỗ lực để thêm một kênh mới và do đó thực hiện channel_insert
- Một vài ms sau, User 2 nỗ lực để thêm cùng một kênh và cũng thực hiện channel_insert
- User 1 của séc các hàng hiện có đã hoàn thành, nhưng trước khi quá trình chèn hoàn tất, kiểm tra của Người dùng 2 hoàn tất và cho biết không có hàng hiện có nào.
Đây có phải là điều kiện chạy tiềm năng trong PostgreSQL không? Cách tốt nhất để giải quyết vấn đề này là gì để tránh các tình huống như vậy? Có thể thực hiện toàn bộ quy trình được lưu trữ một cách nguyên tử hay không, tức là nó chỉ có thể được thực thi một lần cùng một lúc?
Một tùy chọn mà tôi đã cố gắng là tạo trường duy nhất và sau đó cố gắng chèn đầu tiên, và nếu ngoại lệ, hãy chọn mục hiện tại thay thế ... Điều này đã làm việc, tuy nhiên, trường SERIAL sẽ tăng cho mỗi lần thử, để lại rất nhiều khoảng trống trong dãy. Tôi không biết nếu đó sẽ là một vấn đề trong thời gian dài (có lẽ không), nhưng loại gây phiền nhiễu. Có lẽ đây là giải pháp ưa thích?
Cảm ơn mọi phản hồi. Mức độ phép thuật PostgreSQL này vượt xa tôi, vì vậy mọi phản hồi sẽ được đánh giá cao.
Không có vấn đề gì bạn làm, hãy cẩn thận để bình thường hóa định dạng liên kết của bạn để bạn không có vấn đề trường hợp ('Www.Example.Com' và 'www.example .com'), thứ tự tham số issus ('? a = b & c = d' và'? c = d & a = b'), v.v. –
Một vòng lặp chức năng plpgsql trong trường hợp vi phạm khóa trùng lặp có thể đối phó với điều kiện chủng tộc trên phía máy chủ và ở mức cô lập mặc định, là * an toàn * thông thường * rẻ nhất *: http://stackoverflow.com/questions/15939902/is-select-or-insert-in-a-function-prone-to- race-conditions/15950324 # 15950324 –