Đôi khi, cơ sở dữ liệu PostgreSQL cần nhập số lượng lớn dữ liệu trong một hoặc một số bước tối thiểu. Đây thường được gọi là nhập dữ liệu hàng loạt trong đó nguồn dữ liệu thường là một hoặc nhiều tệp lớn. Quá trình này đôi khi có thể chậm đến mức không thể chấp nhận được.
Có nhiều lý do dẫn đến hiệu suất kém như vậy:chỉ mục, trình kích hoạt, khóa ngoại, khóa chính GUID hoặc thậm chí Ghi nhật ký phía trước (WAL) đều có thể gây ra sự chậm trễ.
Trong bài viết này, chúng tôi sẽ đề cập đến một số mẹo thực hành tốt nhất để nhập dữ liệu hàng loạt vào cơ sở dữ liệu PostgreSQL. Tuy nhiên, có thể có những tình huống không có mẹo nào trong số những mẹo này sẽ là giải pháp hiệu quả. Chúng tôi khuyên người đọc nên cân nhắc ưu và nhược điểm của bất kỳ phương pháp nào trước khi áp dụng.
Mẹo 1:Thay đổi Bảng mục tiêu sang Chế độ chưa ghi nhật ký
Đối với PostgreSQL 9.5 trở lên, bảng đích có thể được thay đổi trước tiên thành UNLOGGED, sau đó được thay đổi trở lại LOGGED sau khi dữ liệu được tải:
ALTER TABLE <target table> SET UNLOGGED
<bulk data insert operations…>
ALTER TABLE <target table> LOGGED
Chế độ UNLOGGED đảm bảo PostgreSQL không gửi các thao tác ghi bảng tới Ghi nhật ký phía trước (WAL). Điều này có thể làm cho quá trình tải nhanh đáng kể. Tuy nhiên, vì các hoạt động không được ghi lại, dữ liệu không thể được khôi phục nếu xảy ra sự cố hoặc tắt máy chủ không sạch trong quá trình tải. PostgreSQL sẽ tự động cắt bớt bất kỳ bảng nào được mở khóa khi nó khởi động lại.
Ngoài ra, các bảng đã mở khóa không được sao chép sang các máy chủ dự phòng. Trong những trường hợp như vậy, các bản sao hiện có phải được loại bỏ trước khi tải và tạo lại sau khi tải. Tùy thuộc vào khối lượng dữ liệu trong nút chính và số lượng dự phòng, thời gian để tạo lại bản sao có thể khá lâu và không được chấp nhận bởi các yêu cầu về tính khả dụng cao.
Chúng tôi đề xuất các phương pháp hay nhất sau đây để chèn hàng loạt dữ liệu vào các bảng chưa được ghi nhật ký:
- Tạo một bản sao lưu của bảng và dữ liệu trước khi thay đổi nó sang chế độ chưa được ghi lại
- Tạo lại mọi bản sao cho máy chủ dự phòng sau khi tải xong dữ liệu
- Sử dụng các chèn hàng loạt chưa được ghi lại cho các bảng có thể dễ dàng điền lại (ví dụ:bảng tra cứu lớn hoặc bảng kích thước)
Mẹo 2:Thả và tạo lại chỉ mục
Các chỉ mục hiện có có thể gây ra sự chậm trễ đáng kể trong quá trình chèn dữ liệu hàng loạt. Điều này là do khi mỗi hàng được thêm vào, mục nhập chỉ mục tương ứng cũng phải được cập nhật.
Chúng tôi khuyên bạn nên giảm các chỉ mục trong bảng mục tiêu nếu có thể trước khi bắt đầu chèn hàng loạt và tạo lại các chỉ mục sau khi tải xong. Một lần nữa, việc tạo chỉ mục trên các bảng lớn có thể tốn thời gian, nhưng nói chung sẽ nhanh hơn so với việc cập nhật chỉ mục trong quá trình tải.
DROP INDEX <index_name1>, <index_name2> … <index_name_n>
<bulk data insert operations…>
CREATE INDEX <index_name> ON <target_table>(column1, …,column n)
Có thể đáng giá nếu tạm thời tăng Maint_work_mem tham số cấu hình ngay trước khi tạo chỉ mục. Bộ nhớ làm việc tăng lên có thể giúp tạo chỉ mục nhanh hơn.
Một tùy chọn khác để chơi an toàn là tạo một bản sao của bảng mục tiêu trong cùng một cơ sở dữ liệu với dữ liệu và chỉ mục hiện có. Sau đó, bảng mới được sao chép này có thể được kiểm tra với tính năng chèn hàng loạt cho cả hai trường hợp:thả và tạo lại chỉ mục hoặc cập nhật động chúng. Sau đó, có thể sử dụng phương pháp mang lại hiệu suất tốt hơn cho bảng trực tiếp.
Mẹo 3:Thả và tạo lại các khóa ngoại
Giống như các chỉ mục, các ràng buộc khóa ngoại cũng có thể ảnh hưởng đến hiệu suất tải hàng loạt. Điều này là do mỗi khóa ngoại trong mỗi hàng được chèn phải được kiểm tra sự tồn tại của khóa chính tương ứng. Hậu trường, PostgreSQL sử dụng một trình kích hoạt để thực hiện kiểm tra. Khi tải một số lượng lớn hàng, trình kích hoạt này phải được kích hoạt cho từng hàng, làm tăng thêm chi phí.
Trừ khi bị hạn chế bởi các quy tắc kinh doanh, chúng tôi khuyên bạn nên loại bỏ tất cả các khóa ngoại khỏi bảng đích, tải dữ liệu trong một giao dịch duy nhất, sau đó tạo lại các khóa ngoại sau khi thực hiện giao dịch.
ALTER TABLE <target_table>
DROP CONSTRAINT <foreign_key_constraint>
BEGIN TRANSACTION
<bulk data insert operations…>
COMMIT
ALTER TABLE <target_table>
ADD CONSTRAINT <foreign key constraint>
FOREIGN KEY (<foreign_key_field>)
REFERENCES <parent_table>(<primary key field>)...
Một lần nữa, đang tăng Maint_work_mem tham số cấu hình có thể cải thiện hiệu suất của việc tạo lại các ràng buộc khóa ngoại.
Mẹo 4:Tắt trình kích hoạt
Kích hoạt INSERT hoặc DELETE (nếu quá trình tải cũng liên quan đến việc xóa các bản ghi khỏi bảng đích) có thể gây ra sự chậm trễ khi tải dữ liệu hàng loạt. Điều này là do mỗi trình kích hoạt sẽ có logic cần được kiểm tra và các hoạt động cần hoàn thành ngay sau mỗi hàng được CHÈN hoặc XÓA.
Chúng tôi khuyên bạn nên tắt tất cả các trình kích hoạt trong bảng đích trước khi tải dữ liệu hàng loạt và bật chúng sau khi tải xong. Việc tắt TẤT CẢ các trình kích hoạt cũng bao gồm các trình kích hoạt hệ thống thực thi kiểm tra ràng buộc khóa ngoại.
ALTER TABLE <target table> DISABLE TRIGGER ALL
<bulk data insert operations…>
ALTER TABLE <target table> ENABLE TRIGGER ALL
Mẹo 5:Sử dụng lệnh COPY
Chúng tôi khuyên bạn nên sử dụng PostgreSQL COPY lệnh tải dữ liệu từ một hoặc nhiều tệp. COPY được tối ưu hóa cho tải dữ liệu hàng loạt. Nó hiệu quả hơn việc chạy một số lượng lớn các câu lệnh INSERT hoặc thậm chí là INSERTS nhiều giá trị.
COPY <target table> [( column1>, … , <column_n>)]
FROM '<file_name_and_path>'
WITH (<option1>, <option2>, … , <option_n>)
Các lợi ích khác của việc sử dụng COPY bao gồm:
- Nó hỗ trợ cả nhập tệp văn bản và tệp nhị phân
- Bản chất là giao dịch
- Nó cho phép chỉ định cấu trúc của các tệp đầu vào
- Nó có thể tải dữ liệu có điều kiện bằng mệnh đề WHERE
Mẹo 6:Sử dụng CHÈN đa giá trị
Chạy vài nghìn hoặc vài trăm nghìn câu lệnh INSERT có thể là một lựa chọn tồi cho việc tải dữ liệu hàng loạt. Đó là bởi vì mỗi lệnh INSERT riêng lẻ phải được phân tích cú pháp và chuẩn bị bởi trình tối ưu hóa truy vấn, thực hiện tất cả các kiểm tra ràng buộc, chạy như một giao dịch riêng biệt và đăng nhập vào WAL. Sử dụng một câu lệnh INSERT đơn nhiều giá trị có thể tiết kiệm chi phí này.
INSERT INTO <target_table> (<column1>, <column2>, …, <column_n>)
VALUES
(<value a>, <value b>, …, <value x>),
(<value 1>, <value 2>, …, <value n>),
(<value A>, <value B>, …, <value Z>),
(<value i>, <value ii>, …, <value L>),
...
Hiệu suất INSERT đa giá trị bị ảnh hưởng bởi các chỉ mục hiện có. Chúng tôi khuyên bạn nên bỏ các chỉ mục trước khi chạy lệnh và tạo lại các chỉ mục sau đó.
Một lĩnh vực khác cần lưu ý là dung lượng bộ nhớ có sẵn cho PostgreSQL để chạy các INSERT đa giá trị. Khi chạy INSERT nhiều giá trị, một số lượng lớn giá trị đầu vào phải vừa với RAM và trừ khi có đủ bộ nhớ, quá trình này có thể không thành công.
Chúng tôi khuyên bạn nên đặt effect_cache_size tham số thành 50% và shared_buffer tham số 25% tổng RAM của máy. Ngoài ra, để an toàn, nó chạy một loạt các INSERT nhiều giá trị với mỗi câu lệnh có giá trị cho 1000 hàng.
Mẹo 7:Chạy ANALYZE
Điều này không liên quan đến việc cải thiện hiệu suất nhập dữ liệu hàng loạt, nhưng chúng tôi thực sự khuyên bạn nên chạy ANALYZE lệnh trên bảng đích ngay sau nhập khẩu số lượng lớn. Một số lượng lớn các hàng mới sẽ làm sai lệch đáng kể việc phân phối dữ liệu trong các cột và sẽ khiến mọi thống kê hiện có trên bảng bị lỗi thời. Khi trình tối ưu hóa truy vấn sử dụng thống kê cũ, hiệu suất truy vấn có thể kém đến mức không thể chấp nhận được. Chạy lệnh ANALYZE sẽ đảm bảo mọi thống kê hiện có đều được cập nhật.
Lời kết
Việc nhập hàng loạt dữ liệu có thể không diễn ra hàng ngày đối với một ứng dụng cơ sở dữ liệu, nhưng có tác động về hiệu suất đối với các truy vấn khi nó chạy. Đó là lý do tại sao cần giảm thiểu thời gian tải tốt nhất có thể. Một điều DBA có thể làm để giảm thiểu bất kỳ sự ngạc nhiên nào là kiểm tra tối ưu hóa tải trong môi trường phát triển hoặc môi trường dàn dựng với các thông số kỹ thuật máy chủ và cấu hình PostgreSQL tương tự. Mọi tình huống tải dữ liệu đều khác nhau và tốt nhất bạn nên thử từng phương pháp và tìm phương pháp phù hợp.