2012-04-02 36 views
14

Tôi đang chuyển từ MySql sang Postgres và tôi nhận thấy rằng khi bạn xóa các hàng khỏi MySql, các id duy nhất cho các hàng đó sẽ được sử dụng lại khi bạn tạo các hàng mới. Với Postgres, nếu bạn tạo hàng và xóa chúng, các id duy nhất sẽ không được sử dụng nữa.Trình tự vô hạn PostgreSQL

Có lý do nào cho hành vi này trong Postgres không? Tôi có thể làm cho nó hoạt động giống MySql hơn trong trường hợp này không?

+3

MySQL không nên sử dụng lại ID tự động, trừ khi bạn xóa ID cao nhất. – ceejayoz

+0

Ah! cảm ơn, đó là đúng. OK - Tôi có thể sống với điều đó :) – fatfrog

+7

Bạn cũng không nên quan tâm đến các ID. Họ chỉ là những con số vô nghĩa. –

Trả lời

40

Trình tự có khoảng trống để cho phép chèn đồng thời. Cố gắng tránh khoảng trống hoặc sử dụng lại ID đã xóa sẽ tạo ra các vấn đề hiệu suất khủng khiếp. Xem PostgreSQL wiki FAQ.

PostgreSQL SEQUENCEs được sử dụng để phân bổ ID. Những điều này chỉ tăng lên, và chúng được miễn quy tắc rollback giao dịch thông thường để cho phép nhiều giao dịch lấy các ID mới cùng một lúc. Điều này có nghĩa là nếu một giao dịch quay lại, những ID đó sẽ bị "vứt bỏ"; không có danh sách các ID "miễn phí" được lưu giữ, chỉ là bộ đếm ID hiện tại. Trình tự cũng thường được tăng lên nếu cơ sở dữ liệu tắt không rõ ràng.

Khóa tổng hợp (ID) là vô nghĩa. Thứ tự của họ không đáng kể, tài sản duy nhất của họ về ý nghĩa là duy nhất. Bạn không thể đo lường một cách có ý nghĩa cách hai "xa nhau" hai ID, và bạn cũng không thể nói một cách có ý nghĩa nếu một ID lớn hơn hoặc nhỏ hơn một ID khác. Tất cả những gì bạn có thể làm là nói "bằng" hoặc "không bằng nhau". Bất cứ điều gì khác là không an toàn. Bạn không nên quan tâm đến những khoảng trống.

Nếu bạn cần một chuỗi không có khoảng cách sử dụng lại ID đã xóa, bạn có thể có một ID, bạn chỉ cần từ bỏ một lượng lớn hiệu suất cho nó - cụ thể là bạn không thể có bất kỳ đồng thời nào trên INSERT s vì bạn phải quét bảng để có ID miễn phí thấp nhất, hãy khóa bảng để viết để không có giao dịch nào khác có thể yêu cầu cùng một ID. Hãy thử tìm kiếm "chuỗi không có khoảng cách postgresql".

Cách tiếp cận đơn giản nhất là sử dụng bảng truy cập và hàm nhận ID tiếp theo. Đây là một phiên bản tổng quát sử dụng một bảng truy cập để tạo các ID không có khoảng cách liên tiếp; tuy nhiên, nó không sử dụng lại ID.

CREATE TABLE thetable_id_counter (last_id integer not null); 
INSERT INTO thetable_id_counter VALUES (0); 

CREATE OR REPLACE FUNCTION get_next_id(countertable regclass, countercolumn text) RETURNS integer AS $$ 
DECLARE 
    next_value integer; 
BEGIN 
    EXECUTE format('UPDATE %s SET %I = %I + 1 RETURNING %I', countertable, countercolumn, countercolumn, countercolumn) INTO next_value; 
    RETURN next_value; 
END; 
$$ LANGUAGE plpgsql; 

COMMENT ON get_next_id(countername regclass) IS 'Increment and return value from integer column $2 in table $1'; 

Cách sử dụng:

INSERT INTO dummy(id, blah) 
VALUES (get_next_id('thetable_id_counter','last_id'), 42); 

Lưu ý rằng khi một giao dịch mở đã thu được một ID, tất cả các giao dịch khác mà cố gắng gọi get_next_id sẽ chặn cho đến khi giao dịch 1 cam kết hoặc cuộn lại. Điều này là không thể tránh khỏi và cho ID không có khoảng cách và được thiết kế.

Nếu bạn muốn lưu trữ nhiều bộ đếm cho các mục đích khác nhau trong bảng, chỉ cần thêm tham số vào hàm trên, thêm cột vào bảng truy cập và thêm mệnh đề WHERE vào UPDATE khớp với thông số được thêm vào cột. Bằng cách đó bạn có thể có nhiều hàng truy cập bị khóa độc lập. Do không chỉ cần thêm cột bổ sung cho các bộ đếm mới.

Chức năng này không sử dụng lại ID đã xóa, nó chỉ tránh giới thiệu khoảng trống.

Để sử dụng lại ID tôi khuyên ... không sử dụng lại ID. Nếu bạn thực sự phải, bạn có thể làm như vậy bằng cách thêm một kích hoạt ON INSERT OR UPDATE OR DELETE trên bàn quan tâm thêm ID đã xóa vào bảng bên cạnh danh sách miễn phí và xóa chúng khỏi bảng danh sách miễn phí khi chúng là INSERT ed . Hãy xử lý một số UPDATE dưới dạng DELETE theo sau là INSERT.Bây giờ hãy sửa đổi chức năng tạo ID ở trên để nó hoạt động SELECT free_id INTO next_value FROM free_ids FOR UPDATE LIMIT 1 và nếu được tìm thấy, DELETE s hàng đó. IF NOT FOUND nhận ID mới từ bảng máy phát điện như bình thường. Đây là tiện ích mở rộng chưa được kiểm tra của hàm trước để hỗ trợ sử dụng lại:

CREATE OR REPLACE FUNCTION get_next_id_reuse(countertable regclass, countercolumn text, freelisttable regclass, freelistcolumn text) RETURNS integer AS $$ 
DECLARE 
    next_value integer; 
BEGIN 
    EXECUTE format('SELECT %I FROM %s FOR UPDATE LIMIT 1', freelistcolumn, freelisttable) INTO next_value; 
    IF next_value IS NOT NULL THEN 
     EXECUTE format('DELETE FROM %s WHERE %I = %L', freelisttable, freelistcolumn, next_value); 
    ELSE 
     EXECUTE format('UPDATE %s SET %I = %I + 1 RETURNING %I', countertable, countercolumn, countercolumn, countercolumn) INTO next_value; 
    END IF; 
    RETURN next_value; 
END; 
$$ LANGUAGE plpgsql; 
+0

"cụ thể, bạn không thể có bất kỳ đồng thời nào" --- thực hiện chèn, kiểm tra xem nó có được thực hiện thành công hay không. Không có lý do để khóa toàn bộ bảng (omg) – zerkms

+1

@zerkms Bạn có gợi ý rằng bạn sử dụng một cái gì đó như 'INSERT INTO some_table (id, ...) VALUES ((SELECT tối đa (id) +1 FROM some_table), ...) 'và thử lại trên các lỗi khóa trùng lặp? Nếu vậy, chắc chắn, bạn có thể làm điều đó, nhưng nó sẽ thực hiện không tốt hơn sau đó một cách tiếp cận sử dụng khóa cấp bảng hoặc hàng cấp để tạo khóa, thường tồi tệ hơn do công việc trùng lặp. Về cơ bản nó không thể thực hiện tốt hơn so với một phương pháp tiếp cận dựa trên khóa bởi vì nó vẫn có thể chỉ có một giao dịch thành công bằng văn bản tại bất kỳ thời điểm nào. –

+0

không, tôi muốn nói về việc lấp đầy khoảng trống. Đó là những gì bạn đang nói về, phải không? Chức năng của trình tạo id không được chặn, cũng như quy trình chèn, nhưng với xử lý vi phạm ràng buộc duy nhất. – zerkms