Các đầu ra VACUUM ĐỘNG TỪ, các phiên bản hàng chết không thể bị phá hủy có thể bị xóa chưa?


8

Tôi có Postgres 9.2 DB trong đó một bảng nhất định có rất nhiều hàng chết không thể phá hủy:

# SELECT * FROM public.pgstattuple('mytable');
 table_len  | tuple_count | tuple_len | tuple_percent | dead_tuple_count | dead_tuple_len | dead_tuple_percent | free_space | free_percent 
------------+-------------+-----------+---------------+------------------+----------------+--------------------+------------+--------------
 2850512896 |      283439 | 100900882 |          3.54 |          2537195 |     2666909495 |              93.56 |   50480156 |         1.77
(1 row)

Hút bụi thông thường cũng cho thấy rất nhiều hàng chết không thể di chuyển:

# VACUUM VERBOSE mytable;
[...]
INFO:  "mytable": found 0 removable, 2404332 nonremovable row versions in 309938 out of 316307 pages
DETAIL:  2298005 dead row versions cannot be removed yet.
There were 0 unused item pointers.
0 pages are entirely empty.
CPU 1.90s/2.05u sec elapsed 16.79 sec.
[...]

Bảng chỉ có khoảng 300.000 hàng dữ liệu thực tế, nhưng 2,3 triệu hàng chết (và điều này dường như làm cho các truy vấn nhất định rất chậm).

Theo SELECT * FROM pg_stat_activity where xact_start is not null and datname = 'mydb' order by xact_start;đó không có giao dịch cũ truy cập cơ sở dữ liệu. Các giao dịch cũ nhất đã cũ vài phút và chưa sửa đổi bất cứ điều gì trên bàn.

Tôi cũng đã kiểm tra select * from pg_prepared_xacts(để kiểm tra các giao dịch đã chuẩn bị) và select * from pg_stat_replication(để kiểm tra các bản sao đang chờ xử lý), cả hai đều trống.

Có rất nhiều phần chèn, cập nhật và xóa được thực hiện trên bảng đó, vì vậy tôi có thể hiểu rằng có rất nhiều hàng chết đang được tạo. Nhưng tại sao chúng không bị xóa bởi lệnh VACUUM?


1
đây có phải là một hệ thống sản xuất Có các tùy chọn khác cho VACUUM, như FULL, nhưng cảnh báo: bạn có thể muốn làm điều này khi cơ sở dữ liệu có âm lượng thấp vì nó sẽ khóa bảng. Đọc liên quan: wiki.postgresql.org/wiki/VACUUM_FULL (và lưu ý các chi tiết về thời điểm và nếu) để thực hiện VACUUM FULL và về FILLFACTOR và CLUSTER)
ypercubeᵀᴹ

Bạn đã hoàn thành bài tập về nhà khi cố gắng hút bụi. và kiểm tra các giao dịch lâu dài. 9,2 là một chút cũ? Là nâng cấp lên ổn định 9,6 mới nhất trong khả năng?
Evan Carroll

@EvanCarroll cập nhật có thể có thể, nhưng vẫn còn khá khó khăn. Tôi thực sự không muốn làm điều đó như một thử nghiệm, mà không có bất kỳ dấu hiệu nào cho thấy 9.3+ thực sự có cách khắc phục cho loại vấn đề này.
oliver

2
Các hàng chết không thể di chuyển thường được gây ra bởi các giao dịch chạy dài. Đảm bảo rằng bạn không có bất kỳ kết nối nào với "không hoạt động trong giao dịch" (trong một thời gian dài)
a_horse_with_no_name

@oliver mong nhận được đầu vào cho câu trả lời của tôi.
Evan Carroll

Câu trả lời:


7

Các giao dịch cũ nhất đã cũ vài phút và chưa sửa đổi bất cứ điều gì trên bàn.

Điều đó là không đủ. Tôi nghĩ điều cần thiết để đánh dấu các hàng này là đã chết là khi các giao dịch này được bắt đầu, không có giao dịch nào khác chạm vào các hàng này (thực hiện CẬP NHẬT hoặc XÓA trên chúng).

Cập nhật hoặc xóa một hàng sẽ giữ phiên bản trước của hàng đó ở vị trí cũ và đặt xmaxtrường của nó thành TXID của giao dịch hiện tại. Từ quan điểm của các giao dịch khác, phiên bản cũ của hàng này vẫn hiển thị nếu đó là một phần của ảnh chụp nhanh của họ. Ảnh chụp từng có một xminxmaxđể đó xminxmaxcác phiên bản hàng có thể so sánh. Vấn đề là VACUUM phải so sánh các phiên bản hàng với khả năng hiển thị kết hợp của tất cả các ảnh chụp nhanh trực tiếp, trái ngược với việc kiểm tra đơn giản nếu một thay đổi hàng được cam kết chắc chắn. Cái sau là cần thiết nhưng không đủ để tái chế không gian được sử dụng bởi phiên bản cũ.

Ví dụ: đây là một chuỗi các sự kiện sao cho VACUUM không thể dọn sạch các hàng chết mặc dù giao dịch đã sửa đổi chúng đã kết thúc:

  • t0: Giao dịch chạy dài TX1 bắt đầu
  • t0+30mn: TX2 khởi động và tự đặt ở chế độ REPEATABLE READ.
  • t0+35mn: TX1 kết thúc.
  • t0+40mn: pg_stat_activity chỉ hiển thị TX2 cũ 10 triệu
  • t0+45mn: VACUUM chạy nhưng sẽ không loại bỏ các phiên bản cũ của các hàng được sửa đổi bởi TX1 (vì TX2 có thể cần chúng).

Trong REPEATABLE READtrường hợp đó, nếu đó là trường hợp sau khi TX1 "kết thúc" (cam kết?) ERROR: could not serialize access due to concurrent updateMà không có rollback, TX2 sẽ ngay lập tức nhận được DML tiếp theo (khi txn2 không nhận được khóa trên các hàng được sửa đổi sau ảnh chụp nhanh txn2)?
Evan Carroll

1
@EvanCarroll: Đơn giản hơn thế nhiều, TX2 thậm chí không phải viết bất cứ điều gì và không có khóa hoặc xung đột liên quan, đó chỉ là vấn đề về tầm nhìn. Hệ thống không thể hủy các hàng có thể nhìn thấy khi TX2 khởi động cho đến khi TX2 kết thúc, chỉ vậy thôi.
Daniel Vérité

2
@EvanCarroll: AFAIK truy cập một hàng không đánh dấu bất cứ điều gì. Đối với một điều, hiệu suất đọc sẽ bị hủy nếu mỗi hàng đọc sẽ phát sinh một ghi.
Daniel Vérité

3
Sâu sắc! Hiệu trưởng thậm chí hoạt động mà không có bất kỳ giao dịch chạy dài. Tải cao không ngừng có thể đạt được như vậy.
Erwin Brandstetter

2
Vì vậy, điều này có nghĩa là một "chuỗi" các giao dịch chồng chéo (mỗi lần chèn / cập nhật thực hiện) sẽ ngăn chặn tất cả các hàng chết được tạo bởi một trong các giao dịch chồng chéo? Điều đó thực sự sẽ giải thích những vấn đề tôi gặp phải - phần mềm của tôi thực hiện chính xác kiểu sử dụng này. Mỗi giao dịch của tôi mất ít hơn một phút (và có thể tạo ra 1.000 hàng chết); nhưng toàn bộ chuỗi vẫn không bị phá vỡ trong nhiều ngày.
oliver

6

Tôi đã có thể tạo lại điều này. Về cơ bản, khi bên trong một giao dịch,

  • READ COMMITTEDcấp độ giao dịch mặc định:
  • Trong SERIALIZABLEhoặc REPEATABLE READcấp độ giao dịch:
    • SELECT được một AccessShareLock
    • VACUUMcó thể không dọn dẹp hàng phiên bản chết
    • pg_stat_activity.backend_xmin IS NOT NULL cho giao dịch
    • VERBOSEbáo cáo các hàng này là "phiên bản hàng không thể di chuyển" "phiên bản hàng chết"

Dữ liệu mẫu

CREATE TABLE bar AS
SELECT x::int FROM generate_series(1,10) AS t(x);

Như một lưu ý phụ, nếu bạn xóa bất cứ thứ gì từ barsau khi bạn tạo bảng, những hàng đó sẽ trở thành removableVACUUMbạn sẽ thấy.

INFO:  "bar": removed # row versions in # pages

Trình tự giao dịch

Bây giờ, đây là bảng txn để tạo lại kịch bản.

txn1       - BEGIN; SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
txn1       - SELECT * FROM bar;
      txn2 - DELETE FROM bar;      -- We delete after the select
      txn2 - VACUUM VERBOSE bar;   -- Can't remove the "dead row versions"

VACUUMkhông thể xóa các phiên bản hàng đó vì phần tiếp SELECT * FROM bar;theo REPEATABLE READvẫn sẽ thấy chúng! Các VACUUMsản phẩm trên,

# VACUUM VERBOSE bar;
INFO:  vacuuming "public.bar"
INFO:  "bar": found 0 removable, 10 nonremovable row versions in 1 out of 1 pages
DETAIL:  10 dead row versions cannot be removed yet.
There were 0 unused item pointers.
Skipped 0 pages due to buffer pins.
0 pages are entirely empty.
CPU 0.00s/0.00u sec elapsed 0.00 sec.

Đó chính xác là những gì bạn đang thấy.

Gỡ lỗi

Để tìm ra truy vấn nào ngăn chặn việc VACUUMdọn sạch các hàng chết, hãy chạy nó ..

SELECT query, state,locktype,mode
FROM pg_locks
JOIN pg_stat_activity
  USING (pid)
WHERE relation::regclass = 'bar'::regclass
  AND granted IS TRUE
  AND backend_xmin IS NOT NULL;

Điều này sẽ trả lại một cái gì đó như thế này ..

       query                state         locktype       mode       
────────────────────┼─────────────────────┼──────────┼─────────────────
 SELECT * FROM bar;  idle in transaction  relation  AccessShareLock

Giải pháp

Vì vậy, hãy quay trở lại TXN của chúng tôi .. Chúng tôi cần phải giết / commit / rollback txn1 và chạy lại VACUUM

txn1       - COMMIT;
      txn2 - VACUUM VERBOSE bar;

Và bây giờ chúng ta thấy,

# VACUUM VERBOSE bar;
INFO:  vacuuming "public.bar"
INFO:  "bar": removed 10 row versions in 1 pages
INFO:  "bar": found 10 removable, 0 nonremovable row versions in 1 out of 1 pages
DETAIL:  0 dead row versions cannot be removed yet.
There were 0 unused item pointers.
Skipped 0 pages due to buffer pins.
0 pages are entirely empty.
CPU 0.00s/0.00u sec elapsed 0.00 sec.
INFO:  "bar": truncated 1 to 0 pages
DETAIL:  CPU 0.00s/0.00u sec elapsed 0.01 sec.

Ghi chú đặc biệt

  1. Không quan trọng hàng nào đã bị xóa và hàng nào bạn đã chọn. Việc chọn được ACCESS SHAREkhóa trên bàn. Và, sau đó VACUUMkhông thể xóa các hàng chết để chúng được đánh dấu là "không thể xóa được".
  2. Tôi nghĩ rằng đây là hành vi khá kém cho VACUUM VERBOSE. Tôi rất muốn xem ..

    DETAIL:  10 dead row versions cannot be removed yet
             could not aquire SHARE UPDATE EXCLUSIVE lock on %TABLE
    

đọc thêm

Ngoài ra, cảm ơn Daniel Vérité vì đã khiến tôi xem xét danh mục hệ thống và hành vi của VACUUMcái này.


1
Tuyệt vời bài. Có vẻ như Daniel đóng đinh nó, mặc dù. Và đây là phiên bản cao cấp của câu trả lời của anh ấy với bản demo, nền, liên kết và nhiều lời giải thích hơn.
Erwin Brandstetter

Phân tích tuyệt vời! Tôi không có quyền truy cập vào phần mềm vào cuối tuần, nhưng sẽ kiểm tra điều này vào thứ Hai. Nhưng tôi nghĩ vấn đề không phải là quá nhiều hàng chết được tạo bởi các giao dịch đang chờ xử lý mà là các hàng chết được tạo bởi các giao dịch trong quá khứ đã được hoàn thành từ lâu.
oliver

CHỌN có ACCESS SHAREkhóa trên bàn không ngăn VACUUM lấy SHARE UPDATE EXCLUSIVEkhóa trên bàn đó. Phần "những gì đang xảy ra" của câu trả lời có phần ngược, nó dường như đối với tôi. Ngoài ra câu hỏi khác này: dba.stackexchange.com/questions/21068/ cấp là một cách đọc tốt để làm thế nào khóa quá mạnh có thể ngăn chân không hoạt động, nhưng vanilla đọc không gây ra vấn đề này.
Daniel Vérité

Phải, tốt, một số trong số đó là do điều này không được thực hiện với hệ thống khóa. Tôi đang xem xét điều đó và sau khi tôi có ý tưởng tốt hơn về cách thức hoạt động, tôi sẽ cập nhật thêm. Trên thực tế, khóa mà chân không cần có là một chức năng của backend_xmin. Tôi sẽ ẩn phần đó trong một nhận xét trên trang web, vì tôi đồng ý. Rõ ràng là không đúng. Trong lúc này, hãy giúp tôi trả lời câu hỏi này trên nội bộ: dba.stackexchange.com/q/161050/2639 =)
Evan Carroll

1

Tôi đã phải đối mặt với vấn đề này ngay cả sau khi xác minh rằng cơ sở dữ liệu của tôi không có bất kỳ giao dịch hoạt động hoặc khóa hoạt động nào trên một bảng "foo" nhất định.

Phương pháp sau đây đã xóa thành công tất cả các hàng chết không thể tháo rời khỏi "foo":

CREATE TEMP TABLE temp_foo AS SELECT * FROM "foo";
TRUNCATE TABLE "foo";
INSERT INTO "foo" SELECT * FROM temp_foo;
DROP table temp_foo;

Chỉ cần lưu ý rằng nếu bạn có một bảng lớn có quá nhiều hàng thì đây có thể không phải là giải pháp khả thi, vì tất cả các hàng của bảng được chuyển sang một bảng tạm thời và sau đó được chuyển trở lại bảng ban đầu.

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.