Cách tăng tốc hiệu suất chèn trong PostgreSQL


215

Tôi đang thử nghiệm hiệu suất chèn Postgres. Tôi có một bảng với một cột với số là kiểu dữ liệu của nó. Có một chỉ số trên đó là tốt. Tôi đã điền vào cơ sở dữ liệu bằng truy vấn này:

insert into aNumber (id) values (564),(43536),(34560) ...

Tôi đã chèn 4 triệu hàng rất nhanh 10.000 cùng một lúc với truy vấn ở trên. Sau khi cơ sở dữ liệu đạt 6 triệu hàng, hiệu suất giảm mạnh xuống còn 1 triệu hàng cứ sau 15 phút. Có bất kỳ mẹo để tăng hiệu suất chèn? Tôi cần hiệu suất chèn tối ưu cho dự án này.

Sử dụng Windows 7 Pro trên máy có RAM 5 GB.


5
Cũng đáng đề cập đến phiên bản PG của bạn trong các câu hỏi. Trong trường hợp này, nó không tạo ra nhiều sự khác biệt, nhưng nó có rất nhiều câu hỏi.
Craig Ringer

1
thả các chỉ mục trên bảng và kích hoạt nếu có và chạy tập lệnh chèn. Khi bạn đã hoàn thành tải số lượng lớn, bạn có thể tạo lại các chỉ mục.
Sandeep

Câu trả lời:


481

Xem điền vào cơ sở dữ liệu trong hướng dẫn PostgreSQL, bài viết xuất sắc như thường lệ về chủ đề này và câu hỏi SO này .

(Lưu ý rằng câu trả lời này là về tải dữ liệu hàng loạt vào DB hiện có hoặc để tạo một dữ liệu mới. Nếu bạn quan tâm đến hiệu suất khôi phục DB với pg_restorehoặc psqlthực hiện pg_dumpđầu ra, phần lớn điều này không áp dụng pg_dumppg_restoređã làm những việc như tạo kích hoạt và lập chỉ mục sau khi hoàn thành lược đồ + khôi phục dữ liệu) .

Có rất nhiều việc phải làm. Giải pháp lý tưởng sẽ là nhập vào một UNLOGGEDbảng không có chỉ mục, sau đó thay đổi nó thành nhật ký và thêm các chỉ mục. Thật không may trong PostgreQuery 9.4 không có hỗ trợ để thay đổi bảng từ UNLOGGEDđăng nhập. 9.5 thêm ALTER TABLE ... SET LOGGEDcho phép bạn làm điều này.

Nếu bạn có thể mang cơ sở dữ liệu của mình ngoại tuyến để nhập hàng loạt, hãy sử dụng pg_bulkload.

Nếu không thì:

  • Vô hiệu hóa bất kỳ kích hoạt trên bàn

  • Thả chỉ mục trước khi bắt đầu nhập, tạo lại chúng sau đó. (Phải mất nhiều ít thời gian để xây dựng một chỉ mục trong một đường chuyền hơn là để thêm cùng một dữ liệu để nó dần dần, và chỉ số kết quả là nhiều nhỏ gọn hơn).

  • Nếu thực hiện nhập trong một giao dịch, sẽ an toàn để loại bỏ các ràng buộc khóa ngoại, thực hiện nhập và tạo lại các ràng buộc trước khi cam kết. Không làm điều này nếu quá trình nhập được chia thành nhiều giao dịch vì bạn có thể giới thiệu dữ liệu không hợp lệ.

  • Nếu có thể, sử dụng COPYthay vì INSERTs

  • Nếu bạn không thể sử dụng COPYhãy cân nhắc sử dụng INSERTs đa giá trị nếu thực tế. Bạn dường như đang làm điều này rồi. Đừng cố liệt kê quá nhiều giá trị trong một VALUES; những giá trị đó phải nằm gọn trong bộ nhớ vài lần, vì vậy hãy giữ nó ở mức vài trăm cho mỗi câu lệnh.

  • Batch các phần chèn của bạn vào các giao dịch rõ ràng, thực hiện hàng trăm nghìn hoặc hàng triệu phần chèn cho mỗi giao dịch. Không có giới hạn thực tế AFAIK, nhưng việc tạo khối sẽ cho phép bạn phục hồi từ một lỗi bằng cách đánh dấu sự bắt đầu của từng lô trong dữ liệu đầu vào của bạn. Một lần nữa, bạn dường như đang làm điều này rồi.

  • Sử dụng synchronous_commit=offvà rất lớn commit_delayđể giảm chi phí fsync (). Tuy nhiên, điều này sẽ không giúp được nhiều nếu bạn đã xử lý các công việc của mình thành các giao dịch lớn.

  • INSERThoặc COPYsong song từ một số kết nối. Có bao nhiêu phụ thuộc vào hệ thống con đĩa cứng của bạn; như một quy tắc chung, bạn muốn có một kết nối trên mỗi ổ cứng vật lý nếu sử dụng bộ lưu trữ được gắn trực tiếp.

  • Đặt checkpoint_segmentsgiá trị cao và bật log_checkpoints. Nhìn vào nhật ký PostgreSQL và đảm bảo rằng nó không phàn nàn về các điểm kiểm tra xảy ra quá thường xuyên.

  • Nếu và chỉ khi bạn không mất toàn bộ cụm PostgreQuery (cơ sở dữ liệu của bạn và bất kỳ cụm nào khác trên cùng một cụm) để tham nhũng thảm khốc nếu hệ thống gặp sự cố trong quá trình nhập, bạn có thể dừng PG, đặt fsync=off, bắt đầu PG, thực hiện nhập, sau đó (cực kỳ) dừng PG và thiết lập fsync=onlại. Xem cấu hình WAL . Không làm điều này nếu đã có bất kỳ dữ liệu nào bạn quan tâm trong bất kỳ cơ sở dữ liệu nào trên bản cài đặt PostgreQuery của bạn. Nếu bạn đặt fsync=offbạn cũng có thể thiết lập full_page_writes=off; một lần nữa, chỉ cần nhớ bật lại sau khi nhập để tránh hỏng cơ sở dữ liệu và mất dữ liệu. Xem cài đặt không bền trong hướng dẫn sử dụng.

Bạn cũng nên xem điều chỉnh hệ thống của bạn:

  • Sử dụng ổ SSD chất lượng tốt để lưu trữ càng nhiều càng tốt. SSD tốt với bộ nhớ ghi lại đáng tin cậy, được bảo vệ bằng điện giúp tốc độ cam kết nhanh hơn đáng kể. Chúng sẽ ít có lợi hơn khi bạn làm theo lời khuyên ở trên - giúp giảm số lần xả đĩa / số fsync()s - nhưng vẫn có thể là một trợ giúp lớn. Không sử dụng ổ SSD giá rẻ mà không có bảo vệ mất điện thích hợp trừ khi bạn không quan tâm đến việc giữ dữ liệu của mình.

  • Nếu bạn đang sử dụng RAID 5 hoặc RAID 6 để lưu trữ trực tiếp, hãy dừng ngay bây giờ. Sao lưu dữ liệu của bạn lên, cấu trúc lại mảng RAID của bạn thành RAID 10 và thử lại. RAID 5/6 là vô vọng đối với hiệu năng ghi số lượng lớn - mặc dù bộ điều khiển RAID tốt với bộ đệm lớn có thể giúp ích.

  • Nếu bạn có tùy chọn sử dụng bộ điều khiển RAID phần cứng với bộ đệm ghi lớn được hỗ trợ bằng pin, điều này thực sự có thể cải thiện hiệu suất ghi cho khối lượng công việc với nhiều cam kết. Sẽ không giúp ích nhiều nếu bạn đang sử dụng cam kết async với commit_delay hoặc nếu bạn thực hiện ít giao dịch lớn hơn trong khi tải số lượng lớn.

  • Nếu có thể, lưu trữ WAL ( pg_xlog) trên một mảng đĩa / đĩa riêng. Có rất ít điểm trong việc sử dụng một hệ thống tập tin riêng biệt trên cùng một đĩa. Mọi người thường chọn sử dụng cặp RAID1 cho WAL. Một lần nữa, điều này có tác dụng nhiều hơn đối với các hệ thống có tỷ lệ cam kết cao và sẽ ít ảnh hưởng nếu bạn đang sử dụng bảng chưa được đăng ký làm mục tiêu tải dữ liệu.

Bạn cũng có thể quan tâm đến Tối ưu hóa PostgreSQL để thử nghiệm nhanh .


1
Bạn có đồng ý rằng hình phạt ghi từ RAID 5/6 được giảm nhẹ nếu sử dụng ổ SSD chất lượng tốt? Rõ ràng vẫn còn một hình phạt, nhưng tôi nghĩ rằng sự khác biệt là ít đau đớn hơn nhiều so với ổ cứng.

1
Tôi đã không kiểm tra điều đó. Tôi có thể nói nó có lẽ ít tệ hơn - các hiệu ứng khuếch đại ghi khó chịu và (đối với ghi nhỏ) cần cho chu trình đọc-sửa-ghi vẫn tồn tại, nhưng hình phạt nghiêm trọng cho việc tìm kiếm quá mức sẽ không thành vấn đề.
Craig Ringer

Chẳng hạn, chúng ta có thể vô hiệu hóa các chỉ mục thay vì bỏ chúng, bằng cách cài đặt indisvalid( postgresql.org/docs/8.3/static/catalog-pg-index.html ) thành false, sau đó tải dữ liệu và sau đó đưa chỉ mục trực tuyến bằng cách REINDEX?
Vladislav Rastrusny

1
@CraigRinger Tôi đã thử nghiệm RAID-5 so với RAID-10 với SSD trên Perc H730. RAID-5 thực sự nhanh hơn. Ngoài ra, có thể đáng lưu ý rằng chèn / giao dịch kết hợp với bytea lớn dường như nhanh hơn bản sao. Nói chung lời khuyên tốt.
thích

2
Bất cứ ai cũng đang nhìn thấy bất kỳ cải thiện tốc độ lớn với UNLOGGED? Một thử nghiệm nhanh cho thấy một cái gì đó như cải thiện 10-20%.
serg

15

Sử dụng COPY table TO ... WITH BINARY theo tài liệu là " nhanh hơn một chút so với định dạng văn bản và CSV ." Chỉ làm điều này nếu bạn có hàng triệu hàng để chèn và nếu bạn cảm thấy thoải mái với dữ liệu nhị phân.

Dưới đây là một công thức ví dụ trong Python, sử dụng psycopg2 với đầu vào nhị phân .


1
Chế độ nhị phân có thể tiết kiệm thời gian lớn đối với một số đầu vào, chẳng hạn như dấu thời gian, trong đó phân tích cú pháp chúng là không cần thiết. Đối với nhiều loại dữ liệu, nó không mang lại nhiều lợi ích hoặc thậm chí có thể chậm hơn một chút do băng thông tăng (ví dụ: số nguyên nhỏ). Điểm tốt nâng cao nó.
Craig Ringer

11

Ngoài bài đăng tuyệt vời của Craig Ringer và bài đăng trên blog của depesz, nếu bạn muốn tăng tốc độ chèn của mình thông qua giao diện ODBC ( psqlodbc ) bằng cách sử dụng các câu lệnh được chuẩn bị trong giao dịch, có một số điều bạn cần làm để thực hiện làm lẹ:

  1. Đặt mức độ lỗi quay lại thành "Giao dịch" bằng cách chỉ định Protocol=-1 trong chuỗi kết nối. Theo mặc định, psqlodbc sử dụng mức "Statement", tạo SAVEPOINT cho mỗi câu lệnh thay vì toàn bộ giao dịch, khiến việc chèn chậm hơn.
  2. Sử dụng các câu lệnh được chuẩn bị phía máy chủ bằng cách chỉ định UseServerSidePrepare=1trong chuỗi kết nối. Không có tùy chọn này, máy khách sẽ gửi toàn bộ câu lệnh chèn cùng với mỗi hàng được chèn.
  3. Vô hiệu hóa tự động cam kết trên mỗi câu lệnh bằng cách sử dụng SQLSetConnectAttr(conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast<SQLPOINTER>(SQL_AUTOCOMMIT_OFF), 0);
  4. Khi tất cả các hàng đã được chèn, hãy thực hiện giao dịch bằng cách sử dụng SQLEndTran(SQL_HANDLE_DBC, conn, SQL_COMMIT);. Không cần phải mở một giao dịch rõ ràng.

Thật không may, psqlodbc "thực hiện" SQLBulkOperationsbằng cách đưa ra một loạt các câu lệnh chèn chưa chuẩn bị, để đạt được mục chèn nhanh nhất, người ta cần phải viết mã các bước trên một cách thủ công.


Kích thước bộ đệm ổ cắm lớn, A8=30000000trong chuỗi kết nối cũng nên được sử dụng để tăng tốc độ chèn.
Andrus

9

Tôi đã dành khoảng 6 giờ cho cùng một vấn đề ngày hôm nay. Các phần chèn đi ở tốc độ 'thông thường' (dưới 3 giây trên 100K) cho đến các hàng 5MI (trên tổng số 30MI) và sau đó hiệu suất giảm mạnh (giảm xuống còn 1 phút trên 100K).

Tôi sẽ không liệt kê tất cả những thứ không hoạt động và cắt thẳng vào thịt.

Tôi đã đánh rơi khóa chính trên bảng đích (đó là GUID) và 30MI hoặc hàng của tôi vui vẻ chảy đến đích của chúng với tốc độ không đổi dưới 3 giây trên 100K.


6

Nếu bạn vui lòng chèn colums bằng UUID (không chính xác là trường hợp của bạn) và để thêm vào câu trả lời @Dennis (tôi chưa thể nhận xét), hãy khuyên bạn nên sử dụng gen_random_uuid () (yêu cầu mô-đun PG 9.4 và pgcrypto) là (a nhiều) nhanh hơn uuid_generate_v4 ()

=# explain analyze select uuid_generate_v4(),* from generate_series(1,10000);
                                                        QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------
 Function Scan on generate_series  (cost=0.00..12.50 rows=1000 width=4) (actual time=11.674..10304.959 rows=10000 loops=1)
 Planning time: 0.157 ms
 Execution time: 13353.098 ms
(3 filas)

đấu với


=# explain analyze select gen_random_uuid(),* from generate_series(1,10000);
                                                        QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------
 Function Scan on generate_series  (cost=0.00..12.50 rows=1000 width=4) (actual time=252.274..418.137 rows=10000 loops=1)
 Planning time: 0.064 ms
 Execution time: 503.818 ms
(3 filas)

Ngoài ra, đó là cách chính thức được đề xuất để làm điều đó

Ghi chú

Nếu bạn chỉ cần UUID được tạo ngẫu nhiên (phiên bản 4), hãy xem xét sử dụng hàm gen_random_uuid () từ mô-đun pgcrypto thay thế.

Thời gian chèn này giảm từ ~ 2 giờ xuống ~ 10 phút cho 3,7M hàng.


1

Để có hiệu suất Chèn tối ưu, hãy tắt chỉ mục nếu đó là một tùy chọn dành cho bạn. Ngoài ra, phần cứng (đĩa, bộ nhớ) tốt hơn cũng hữu ích


-1

Tôi đã gặp vấn đề hiệu suất chèn này là tốt. Giải pháp của tôi là sinh ra một số thói quen đi để hoàn thành công việc chèn. Trong khi đó, SetMaxOpenConnsnên được cung cấp một số thích hợp nếu không sẽ có quá nhiều lỗi kết nối mở.

db, _ := sql.open() 
db.SetMaxOpenConns(SOME CONFIG INTEGER NUMBER) 
var wg sync.WaitGroup
for _, query := range queries {
    wg.Add(1)
    go func(msg string) {
        defer wg.Done()
        _, err := db.Exec(msg)
        if err != nil {
            fmt.Println(err)
        }
    }(query)
}
wg.Wait()

Tốc độ tải nhanh hơn nhiều cho dự án của tôi. Đoạn mã này chỉ đưa ra một ý tưởng về cách thức hoạt động của nó. Người đọc có thể sửa đổi nó một cách dễ dàng.


Vâng, bạn có thể nói rằng. Nhưng nó làm giảm thời gian chạy từ vài giờ xuống vài phút cho hàng triệu hàng cho trường hợp của tôi. :)
Patrick
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.