Tìm hiểu tầng database trong Rails
Trừu tượng là một điều tuyệt vời. Trong Rails: chúng ta có thể đạt được số lượng lớn các chức năng với tương đối ít dòng code. Ví dụ, tôi không nhất thiết phải biết rất nhiều về cách cơ sở dữ liệu thực hiện để được hoạt động và thực thi một cách nhanh chóng. Nhược điểm của việc tách biệt với chức ...
Trừu tượng là một điều tuyệt vời. Trong Rails: chúng ta có thể đạt được số lượng lớn các chức năng với tương đối ít dòng code. Ví dụ, tôi không nhất thiết phải biết rất nhiều về cách cơ sở dữ liệu thực hiện để được hoạt động và thực thi một cách nhanh chóng.
Nhược điểm của việc tách biệt với chức năng cốt lõi này là các nhà phát triển không cần phải học tất cả những điều có lẽ nên biết. Tôi có thể khắc phục được vấn đề truy vấn chậm bằng cách xóa chỉ mục bởi vì một người nào đó viết nên theo dòng này đã không hiểu được cardinality hoặc cách một chỉ mục hoạt động. Trong bài viết này, chúng ta sẽ tìm hiểu sâu về một vài khía cạnh trong cơ sở dữ liệu
Cách indexs hoạt động
Chúng tôi hy vọng nhanh chóng tìm ra thời gian khi truy vấn một cơ sở dữ liệu. Nếu bạn đang thực hiện một hàm để thêm vào mỗi giá trị giữa hai số, bạn có thể kiểm tra từng giá trị để xem nó có phù hợp với điều kiện không, nhưng điều này sẽ không hiệu quả đối với các tập dữ liệu lớn vì nó thực hiện trong thời gian tuyến tính (O n)). Những gì bạn cần là một cách để lưu trữ data theo một trật tự nhất định để tạo điều kiện thuận lợi cho thời gian truy cập nhanh hơn. Một cấu trúc giống như cây tìm kiếm nhị phân (BST) đáp ứng được điều này, bằng cách đảm bảo rằng bất kỳ bản ghi nào cho trước lớn hơn tất cả các bản ghi trong cây con bên trái của nó, nhưng nhỏ hơn tất cả các mục trong cây con bên phải của nó. Điều này làm cho thời gian tìm kiếm của chúng ta xuống đến một O (log n) và trở thành xương sống của một chỉ mục (index) cơ sở dữ liệu.
Tuy nhiên chúng tôi ít khi yêu cầu một bản ghi cụ thể. Thay vào đó, chúng tôi muốn tất cả các bản ghi phù hợp với một tiêu chí cụ thể. Một giải pháp ban đầu sẽ là kiểm tra từng bản ghi một lần nữa, đưa ra kết quả là thời gian tuyến tính, hoặc O (n). Những gì chúng ta cần là một cách để lưu trữ tất cả các bản ghi theo thứ tự, duyệt qua danh sách đó và sau đó dừng lại khi một điều kiện được đáp ứng. Cấu trúc dữ liệu phù hợp với nhiệm vụ này là B+ Tree, với thời gian truy cập là O (m + log n).
Tôi thấy, với cách tiếp cận này khá thiếu hiệu quả. Tất cả những gì cần thiết là lập chỉ mục nhanh chóng của một danh sách có thứ tự - một công việc phù hợp với cấu trúc dữ liệu chưa được sử dụng và ít được biết đến, Skip list. Nó chỉ ra rằng, mặc dù B+ Tree thường đòi hỏi nhiều công việc của CPU hơn so với các hoạt động tương đương trong các cấu trúc dữ liệu khác, chúng vượt trội yêu cầu đòi hỏi phải chuyển từ đĩa vào bộ nhớ ít hơn, đây là một nút cổ chai tự nhiên. Bài đăng trên Google Blog này cung cấp một số bối cảnh về lý do tại sao. Ngoài ra, B+ Tree cũng là trường hợp hiệu quả xấu nhất - một vấn đề rất lớn khi dữ liệu trở nên lớn hơn.
Tại sao sử dụng quá nhiều index là một ý tưởng tồi?
Hãy tưởng tượng xóa một hàng trong cơ sở dữ liệu của chúng tôi. Điều này có nghĩa là chúng tôi cũng cần phải xóa nút tương ứng trong cây B +. Bạn không thể loại bỏ nút, bởi vì chúng ta dựa vào tính chất có trật tự của cấu trúc dữ liệu, điều đó có nghĩa là chúng ta cần phải duy trì thứ tự đó cùng với việc đảm bảo rằng chiều cao tổng thể của cây thấp đến mức có thể. Để chống lại điều này, công cụ cơ sở dữ liệu của chúng tôi sẽ sử dụng phương pháp chèn và xóa thông minh, nhưng sẽ mất thời gian logarithmic (O (log n)). Quan trọng nhất là bất kỳ trường nào bạn áp dụng một chỉ mục đều phải cân bằng lại cây là mỗi lần bạn thực hiện thao tác chèn hoặc xóa.
Một lĩnh vực với ít lựa chọn có thể (như boolean, trilean, vv) được cho là có tỉ lệ phải chỉnh sửa toàn bộ tree là thấp. Dưới đây là một bài báo của IBM cảnh báo về những cạm bẫy của các lĩnh vực lập chỉ mục với cardinality thấp, nhưng bản chất của nó là bạn nên tránh làm điều đó. Tuy nhiên, tôi đã giảm được 80% các truy vấn trước, chỉ đơn giản bằng cách đánh index cho cột True / False.
Bộ phân tích truy vấn
Hãy lấy một truy vấn bình thường và áp dụng nó
SELECT id, email, admin, credits FROM users WHERE credits > 2 + 3;
Theo một cách tương tự với bộ compiler, bộ phân tích cú pháp sẽ thay đổi truy vấn của bạn thành một cây phân tích cú pháp:
>>> SELECT email, admin, credits FROM users WHERE credits > 2 + 3 LOG: parse tree: DETAIL: {QUERY :commandType 1 :querySource 0 :canSetTag true :utilityStmt <> :resultRelation 0 :hasAggs false :hasWindowFuncs false :hasSubLinks false :hasDistinctOn false :hasRecursive false :hasModifyingCTE false :hasForUpdate false :hasRowSecurity false :cteList <> :rtable ( {RTE :alias <> :eref {ALIAS :aliasname users :colnames ("id" "created_at" "updated_at" "email" "encrypted_password " "confirmation_token" "remember_token" "admin" "" "credits") } :rtekind 0 :relid 27043 :relkind r :tablesample <> :lateral false :inh true :inFromCl true :requiredPerms 2 :checkAsUser 0 :selectedCols (b 12 16 18) :insertedCols (b) :updatedCols (b) :securityQuals <> Time: 0.001s
Ta có thể sử dụng pgcli cho Postgres và hai cài đặt sau:
SET client_min_messages=debug1; SET debug_print_parse=on;
Quay lại trình phân tích truy vấn. Trong quá trình này, nó kiểm tra rằng đầu vào của bạn là hợp pháp. Từ khoá đi theo có thứ tự sai? Typo? Đó là bộ truy vấn phân tích cú pháp (lưu ý WHRE trong ví dụ sau):
SELECT email, admin, credits FROM users WHRE credits > 2 + 3; syntax error at or near "credits"
Tiếp theo, bộ phân tích truy vấn sẽ kiểm tra siêu dữ liệu cơ sở dữ liệu: bảng của bạn có tồn tại không? Cột tồn tại không? Yêu cầu của bạn có thể thực hiện được không? Bạn có ủy quyền thông qua hệ thống kiểm soát cơ sở dữ liệu không?
SELECT email, admin, credits FROM users WHERE credits > 'Ostrich'; invalid input syntax for integer: "Ostrich"
Nếu mọi thứ đều tốt, thì trình phân tích truy vấn sẽ chuyển qua tree theo bước tiếp theo.
Ghi lại truy vấn
Với cây của chúng ta được xây dựng thành công, đó là thời gian để tối ưu hóa nó. Người ghi lại truy vấn lấy cây của chúng ta và áp dụng một danh sách các quy tắc cho nó. Ví dụ: nếu bạn đã đặt DISTINCT trên một cột đã có một ràng buộc duy nhất, thì DISTINCT sẽ bị loại bỏ. Tính toán số nguyên được thay bằng một hằng số (2 + 3 trong ví dụ của chúng ta được thay bằng một 5 ở đây). Có rất nhiều quy tắc khác ở đây, nhưng kết cục là để đảm bảo rằng các chu kỳ đồng hồ phát sinh từ việc thực hiện truy vấn được giữ ở mức tối thiểu.
Kết quả cuối cùng là một kế hoạch truy vấn, về mặt lý thuyết, một truy vấn SQL được tối ưu hoá và hiệu quả cao. Nhưng làm thế nào để các công cụ DB biết đó là hiệu quả nhất? Vì vậy, chúng ta cần phải có quá trình xem lại nhanh chóng
Các thống kê
Một công cụ cơ sở dữ liệu tốt sẽ thu thập số liệu thống kê. Bao gồm số lượng bản ghi trong một bảng, có bao nhiêu giá trị riêng biệt nằm trong cột, giá trị tối thiểu / tối đa, v.v. Đây là quy trình tối ưu hóa truy vấn và đó là lý do giúp truy vấn nhanh chóng. Ví dụ: nếu bạn đang cố gắng join một bảng với một triệu row cho một bảng với một trăm, thì sẽ nhanh hơn rất nhiều để tham gia vào một bảng có ít bản ghi hơn. Nó thực sự nhanh hơn nhiều khi join với một bảng có ít record hơn, các biểu đồ tính toán có chứa thông tin như chất lượng và độ lệch tiêu chuẩn, từ đó giúp bạn có được ý tưởng.
Không có gì có thể làm giảm các truy vấn nhanh hơn là bằng các số liệu thống kê sai lầm. Nếu người tối ưu hóa truy vấn nghĩ rằng có 200 bản ghi khi thực sự có 200 triệu và đã tính biểu đồ và số liệu thống kê trên cơ sở này, đây có thể là sự khác biệt giữa phút và giờ để thực hiện truy vấn. Vì lý do này, điều quan trọng là cần thu thập thống kê theo định kỳ.
Thực hiện truy vấn
Cuối cùng, đã đến lúc thực hiện truy vấn của chúng ta! Trình quản lý truy vấn kiểm tra nó có đủ tài nguyên (bộ nhớ và CPU) và nếu được phép, bắt đầu thực hiện. Đầu tiên nó sẽ kiểm tra bộ nhớ trong bộ nhớ cache của dữ liệu (bộ nhớ nhanh hơn nhiều so với đĩa) và kích hoạt trình quản lý bộ nhớ cache để tìm nạp trước các đoạn tiếp theo của dữ liệu cần thiết, được chỉ ra bởi kế hoạch truy vấn. Nó sẽ loại bỏ dữ liệu không còn cần thiết và bạn có thể kiểm tra xem nó có thực hiện tốt hay không bằng cách dựa vào tỉ lệ buffer/cache-hit ratio. Khoảng 99% là những gì bạn đang nhắm tới.
Nếu có quá trình viết / cập nhật / xoá, thì trình quản lý transaction của bạn sẽ kích hoạt ở đây để bảo vệ tính toàn vẹn của dữ liệu của bạn, nhưng vì chúng ta chỉ đọc một cách đơn giản nên tất cả những gì còn lại là dành cho người quản lý cơ sở dữ liệu trả lại một phản hồi cho khách hàng.
Kết luận
Có rất nhiều nguồn tuyệt vời để hiểu rõ hơn về cơ sở dữ liệu, và có rất nhiều điều tôi không đề cập ở đây. Nhưng qua bài viết này hy vọng sẽ cung cấp một cách tổng quan để giúp bạn bắt đầu hiểu những gì cần tìm khi profiling. Ít nhất, bạn có thể suy nghĩ kỹ hơn khi cần phải thêm một index
Nguồn bài viết: Beyond Rails Abstractions: A Dive into Database Internals