Cách tốt nhất để điền một cột mới trong một bảng lớn?


33

Chúng tôi có một bảng 2,2 GB trong Postgres với 7.801.611 hàng trong đó. Chúng tôi đang thêm một cột uuid / hướng dẫn vào nó và tôi tự hỏi cách tốt nhất để điền vào cột đó là gì (vì chúng tôi muốn thêm một NOT NULLràng buộc cho nó).

Nếu tôi hiểu chính xác Postgres thì một bản cập nhật về mặt kỹ thuật là xóa và chèn, do đó, về cơ bản, nó sẽ xây dựng lại toàn bộ bảng 2.2 gb. Ngoài ra, chúng tôi có một nô lệ đang chạy nên chúng tôi không muốn điều đó bị tụt lại phía sau.

Có cách nào tốt hơn là viết một kịch bản mà từ từ đưa nó theo thời gian không?


2
Bạn đã chạy một ALTER TABLE .. ADD COLUMN ...hoặc là phần đó sẽ được trả lời là tốt?
ypercubeᵀᴹ

Chưa chạy bất kỳ sửa đổi bảng nào, chỉ trong giai đoạn lập kế hoạch. Tôi đã làm điều này trước khi thêm cột, điền vào nó, sau đó thêm ràng buộc hoặc chỉ mục. Tuy nhiên, bảng này lớn hơn đáng kể và tôi lo lắng về tải, khóa, sao chép, v.v ...
Collin Peters

Câu trả lời:


45

Nó rất nhiều phụ thuộc vào các chi tiết yêu cầu của bạn.

Nếu bạn có đủ dung lượng trống (ít nhất 110% pg_size_pretty((pg_total_relation_size(tbl))) trên đĩa và có thể đủ khả năng khóa chia sẻ trong một thời giankhóa độc quyền trong một thời gian rất ngắn , thì hãy tạo một bảng mới bao gồm cả uuidcột sử dụng CREATE TABLE AS. Tại sao?

Đoạn mã dưới đây sử dụng một chức năng từ uuid-ossmô-đun bổ sung .

  • Khóa bảng chống lại các thay đổi đồng thời trong SHAREchế độ (vẫn cho phép đọc đồng thời). Nỗ lực viết lên bàn sẽ chờ đợi và cuối cùng thất bại. Xem bên dưới.

  • Sao chép toàn bộ bảng trong khi điền vào cột mới một cách nhanh chóng - có thể sắp xếp các hàng thuận lợi trong khi ở đó.
    Nếu bạn sắp xếp lại các hàng, hãy đảm bảo đặt work_memcao nhất có thể (chỉ cho phiên của bạn, không phải trên toàn cầu).

  • Sau đó thêm các ràng buộc, khóa ngoại, chỉ mục, kích hoạt, vv vào bảng mới. Khi cập nhật phần lớn của một bảng nó là nhiều nhanh hơn để tạo ra các chỉ số từ đầu hơn là thêm hàng lặp đi lặp lại.

  • Khi bảng mới đã sẵn sàng, bỏ cái cũ và đổi tên cái mới để biến nó thành một cái thay thế thả vào. Chỉ bước cuối cùng này có được một khóa độc quyền trên bảng cũ cho phần còn lại của giao dịch - hiện tại sẽ rất ngắn.
    Nó cũng yêu cầu bạn xóa bất kỳ đối tượng nào tùy thuộc vào loại bảng (dạng xem, hàm sử dụng loại bảng trong chữ ký, ...) và tạo lại chúng sau đó.

  • Làm tất cả trong một giao dịch để tránh các trạng thái không đầy đủ.

BEGIN;
LOCK TABLE tbl IN SHARE MODE;

SET LOCAL work_mem = '???? MB';  -- just for this transaction

CREATE TABLE tbl_new AS 
SELECT uuid_generate_v1() AS tbl_uuid, <list of all columns in order>
FROM   tbl
ORDER  BY ??;  -- optionally order rows favorably while being at it.

ALTER TABLE tbl_new
   ALTER COLUMN tbl_uuid SET NOT NULL
 , ALTER COLUMN tbl_uuid SET DEFAULT uuid_generate_v1()
 , ADD CONSTRAINT tbl_uuid_uni UNIQUE(tbl_uuid);

-- more constraints, indices, triggers?

DROP TABLE tbl;
ALTER TABLE tbl_new RENAME tbl;

-- recreate views etc. if any
COMMIT;

Điều này nên được nhanh nhất. Bất kỳ phương pháp cập nhật nào khác cũng phải viết lại toàn bộ bảng, chỉ trong một kiểu đắt tiền hơn. Bạn sẽ chỉ đi theo tuyến đường đó nếu bạn không có đủ dung lượng trống trên đĩa hoặc không đủ khả năng để khóa toàn bộ bảng hoặc tạo ra lỗi cho các nỗ lực ghi đồng thời.

Điều gì xảy ra để viết đồng thời?

Giao dịch khác (trong các phiên khác) cố gắng INSERT/ UPDATE/ DELETEtrong cùng một bảng sau khi giao dịch của bạn đã thực hiện SHAREkhóa, sẽ đợi cho đến khi khóa được phát hành hoặc hết thời gian chờ, bất cứ khi nào đến trước. Dù sao thì họ cũng sẽ thất bại , vì cái bàn mà họ đang cố viết đã bị xóa khỏi chúng.

Bảng mới có bảng OID mới, nhưng giao dịch đồng thời đã phân giải tên bảng thành OID của bảng trước đó . Khi khóa cuối cùng được phát hành, họ cố gắng tự khóa bảng trước khi viết cho nó và thấy rằng nó đã biến mất. Postgres sẽ trả lời:

ERROR: could not open relation with OID 123456

Trong trường hợp 123456là OID của bảng cũ. Bạn cần nắm bắt ngoại lệ đó và thử lại các truy vấn trong mã ứng dụng của mình để tránh nó.

Nếu bạn không đủ khả năng để điều đó xảy ra, bạn phải giữ bảng gốc của mình.

Hai lựa chọn thay thế giữ bảng hiện có

  1. Cập nhật tại chỗ (có thể chạy cập nhật trên các phân đoạn nhỏ tại một thời điểm) trước khi bạn thêm các NOT NULLràng buộc. Thêm một cột mới với các giá trị NULL và không có NOT NULLràng buộc là rẻ.
    Kể từ Postgres 9.2, bạn cũng có thể tạo một CHECKràng buộc vớiNOT VALID :

    Các ràng buộc vẫn sẽ được thi hành đối với các lần chèn hoặc cập nhật tiếp theo

    Điều đó cho phép bạn cập nhật các hàng peu à peu - trong nhiều giao dịch riêng biệt . Điều này tránh việc giữ khóa hàng quá lâu và nó cũng cho phép các hàng chết được sử dụng lại. (Bạn sẽ phải chạy VACUUMthủ công nếu không có đủ thời gian ở giữa để tự động khởi động.) Cuối cùng, thêm NOT NULLràng buộc và xóa NOT VALID CHECKràng buộc:

    ALTER TABLE tbl ADD CONSTRAINT tbl_no_null CHECK (tbl_uuid IS NOT NULL) NOT VALID;
    
    -- update rows in multiple batches in separate transactions
    -- possibly run VACUUM between transactions
    
    ALTER TABLE tbl ALTER COLUMN tbl_uuid SET NOT NULL;
    ALTER TABLE tbl ALTER DROP CONSTRAINT tbl_no_null;
    

    Câu trả lời liên quan thảo luận NOT VALIDchi tiết hơn:

  2. Chuẩn bị trạng thái mới trong một bảng tạm thời , TRUNCATEbản gốc và nạp tiền từ bảng temp. Tất cả trong một giao dịch . Bạn vẫn cần phải SHAREkhóa trước khi chuẩn bị bảng mới để tránh mất ghi đồng thời.

    Chi tiết trong các câu trả lời liên quan trên SO:


Câu trả lời tuyệt vời! Chính xác là thông tin tôi đang tìm kiếm. Hai câu hỏi 1. Bạn có ý tưởng nào về một cách dễ dàng để kiểm tra một hành động như thế này sẽ kéo dài bao lâu không? 2. Nếu mất 5 phút, điều gì sẽ xảy ra với các hành động cố gắng cập nhật một hàng trong bảng đó trong 5 phút đó?
Collin Peters

@CollinPeter: 1. Chia sẻ thời gian của con sư tử sẽ đi vào việc sao chép bảng lớn - và có thể tái tạo các chỉ số và ràng buộc (tùy thuộc). Thả và đổi tên là giá rẻ. Để kiểm tra, bạn có thể chạy tập lệnh SQL đã chuẩn bị của mình mà không cần LOCKđến và loại trừ DROP. Tôi chỉ có thể thốt ra những phỏng đoán hoang dã và vô dụng. Đối với 2., xin vui lòng xem xét phần phụ lục cho câu trả lời của tôi.
Erwin Brandstetter

@ErwinBrandstetter Tiếp tục tạo lại các chế độ xem, vì vậy nếu tôi có hàng tá chế độ xem vẫn sử dụng bảng cũ (oid) sau khi đổi tên bảng. Có cách nào để thực hiện thay thế sâu hơn là chạy lại toàn bộ chế độ xem / tạo lại chế độ xem không?
CodeFarmer

@CodeFarmer: Nếu bạn chỉ đổi tên bảng, các khung nhìn tiếp tục hoạt động với bảng đã đổi tên. Để tạo chế độ xem sử dụng bảng mới thay thế, bạn cần tạo lại chúng dựa trên bảng mới. (Cũng để cho phép xóa bảng cũ.) Không có cách nào (thực tế) xung quanh nó.
Erwin Brandstetter

14

Tôi không có câu trả lời "tốt nhất", nhưng tôi có câu trả lời "ít tệ nhất" có thể cho phép bạn hoàn thành công việc một cách hợp lý nhanh chóng.

Bảng của tôi có các hàng 2MM và hiệu suất cập nhật bị rối khi tôi cố thêm cột dấu thời gian thứ cấp mặc định vào cột đầu tiên.

ALTER TABLE mytable ADD new_timestamp TIMESTAMP ;
UPDATE mytable SET new_timestamp = old_timestamp ;
ALTER TABLE mytable ALTER new_timestamp SET NOT NULL ;

Sau khi nó được treo trong 40 phút, tôi đã thử điều này trên một lô nhỏ để có ý tưởng về việc điều này có thể kéo dài bao lâu - dự báo là khoảng 8 giờ.

Câu trả lời được chấp nhận chắc chắn tốt hơn - nhưng bảng này được sử dụng nhiều trong cơ sở dữ liệu của tôi. Có vài chục bảng FKEY trên đó; Tôi muốn tránh chuyển các phím NGOẠI TỆ trên rất nhiều bảng. Và sau đó là quan điểm.

Một chút tìm kiếm tài liệu, nghiên cứu trường hợp và StackOverflow, và tôi đã có "A-Ha!" chốc lát. Cống không nằm trong CẬP NHẬT cốt lõi, mà trên tất cả các hoạt động INDEX. Bảng của tôi có 12 chỉ mục trên đó - một số cho các ràng buộc duy nhất, một số để tăng tốc trình lập kế hoạch truy vấn và một số cho tìm kiếm toàn văn bản.

Mỗi hàng được CẬP NHẬT không chỉ hoạt động trên XÓA / XÁC NHẬN, mà còn là chi phí thay đổi từng chỉ số và kiểm tra các ràng buộc.

Giải pháp của tôi là bỏ mọi chỉ mục và ràng buộc, cập nhật bảng, sau đó thêm tất cả các chỉ mục / ràng buộc trở lại.

Mất khoảng 3 phút để viết một giao dịch SQL đã làm như sau:

  • BẮT ĐẦU;
  • giảm chỉ số / chòm sao
  • bảng cập nhật
  • thêm lại các chỉ mục / ràng buộc
  • CAM KẾT;

Kịch bản mất 7 phút để chạy.

Câu trả lời được chấp nhận chắc chắn là tốt hơn và đúng đắn hơn ... và hầu như loại bỏ sự cần thiết của thời gian chết. Tuy nhiên, trong trường hợp của tôi, sẽ cần nhiều công việc "Nhà phát triển" hơn để sử dụng giải pháp đó và chúng tôi đã có một cửa sổ thời gian ngừng hoạt động 30 phút mà nó có thể được thực hiện. Giải pháp của chúng tôi đã giải quyết trong 10.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.