Như đã giới thiệu ở bài viết trước, MongoDB là một CSDL có khả năng mở rộng, hiệu suất cao, mã nguồn mở và hướng văn bản. Tiếp theo, bài viết này sẽ hướng dẫn các bạn một số cách sử dụng MongoDB như thiết kế lược đồ, nhúng và tham chiếu, lựa chọn các chỉ mục và truy vấn,…
Cùng tìm tìm hiểu cách ứng dụng MongoDB trong Hệ quản trị cơ sở dữ liệu nhé!
Thiết kế lược đồ
Với MongoDB, chúng ta ít phải “chuẩn hóa” hơn so với khi làm việc với lược đồ quan hệ vì trong MongoDB không có khái niệm liên kết (join). Nói chung, với mỗi đối tượng (object) mức cao nhất, ta sẽ có một bộ sưu tập (collection) dữ liệu.
Một bộ sưu tập không phải cho tất cả các lớp (class), mà các đối tượng sẽ được nhúng vào đó. So sánh với lược đồ quan hệ, ta cần lưu Score vào bảng riêng và dùng khóa ngoài liên kết với Student.
Nhúng hay tham chiếu
Một câu hỏi quan trọng trong thiết kế lược đồ MongoDB là: “Đối tượng này có cần một bộ sưu tập của riêng nó không hay nên nhúng vào trong các đối tượng trong các bộ sưu tập khác?” Trong cơ sở dữ liệu quan hệ, mỗi tiểu mục có thể trở thành một bảng riêng biệt. Trong MongoDB, nó không được khuyến cáo, việc nhúng các đối tượng hiệu quả hơn nhiều. Chúng ta cũng có thể đặt ra câu hỏi “Tại sao tôi không muốn nhúng đối tượng này?”
Tại sao tham chiếu lại chậm. Ta xem ví dụ sau. Chúng ta có một đối tượng Student và cần thực hiện:
print( students.address.city );
Phép toán này sẽ luôn được thực hiện nhanh nếu Address là một đối tượng nhúng, và được lưu ở RAM nếu Student được lưu ở RAM.
Tuy nhiên, với truy vấn:
print( students.scores[0].for_course.name );
Nếu đó là lần đầu truy cập đến khóa này thì trình tiện ích phải thực hiện truy vấn:
students.scores[0].for_course = db.courses.findOne({_id:_course_id_to_find_});
Các luật cơ bản
- Các đối tượng “lớp thứ nhất” là các đối tượng ở mức cao nhất, có bộ sưu tập của riêng mình.
- Các đối tượng miêu tả chi tiết các mục thường được nhúng
- Các đối tượng mà theo mô hình đối tượng có chứa quan hệ nói chung nên được nhúng
- Quan hệ nhiều – nhiều thường được tham chiếu.
- Các bộ sưu tập chỉ với một vài đối tượng có thể tồn tại một cách an toàn giống như bộ sưu tập riêng lẻ, được lưu trữ nhanh chóng trong bộ nhớ máy chủ ứng dụng.
- Các đối tượng nhúng khó khăn để tham chiếu hơn là các đối tượng mức cao.
- Sẽ khó khăn hơn để có một cái nhìn mức hệ thống đối với các đối tượng nhúng. Ví dụ: Sẽ dễ thực hiện truy vấn tìm 100 sinh viên có điểm cao nhất hơn nếu Score không bị nhúng.
- Nếu dữ liệu được nhúng lớn, có thể đạt đến giới hạn kích thước của một đối tượng.
- Nếu hiệu suất là quan trọng, hãy nhúng.
Một số ví dụ
- Customer/Order/ Order Line-Item: Customers, Orders nên có một bộ sưu tập riêng. Line-Items nên là một mảng các mục cần mua và được nhúng trong đối tượng Order
- Hệ thống Blog: Posts cần có bộ sưu tập riêng. Post Author có thể có bộ sưu tập riêng hoặc nếu đơn giản chỉ là địa chỉ mail của tác giả thì cho thành một trường trong Posts. Comments được nhúng trong Posts
Lựa chọn chỉ mục
Một khía cạnh thứ hai khi thiết kế lược đồ là việc lựa chọn chỉ mục. Việc đánh chỉ mục làm cho việc thực hiện truy vấn nhanh hơn. Một truy vấn bình thường cần vài phút, có thể được thực hiện ngay lập tức với việc sử dụng chỉ mục.
Trong MongoDB:
- Trường _id được đánh chỉ mục tự động.
- Những trường mà theo đó các khóa được tìm kiếm nên được đánh chỉ mục.
- Những trường sắp xếp nói chung nên được đánh chỉ mục.
Lưu ý rằng việc thêm vào chỉ mục chỉ làm chậm quá trình ghi vào bộ sưu tập mà không làm chậm quá trình đọc. Vì vậy, sử dụng nhiều chỉ mục với những bộ sưu tập mà tỉ lệ read:write cao. Với những bộ sưu tập mà ghi nhiều hơn đọc, sử dụng chỉ mục là rất tốn kém.
Chỉ mục
Chỉ mục làm tăng hiệu suất truy vấn lên rất nhiều. Điều quan trọng là nghĩ xem xét tất cả các loại truy vấn cần trong ứng dụng để xác định những chỉ mục liên quan. Khi đã xác định xong, việc tạo ra các chỉ mục trong MongoDB là khá dễ dàng.
Các khái niệm cơ bản
Chỉ mục là một cấu trúc dữ liệu, thu thập thông tin về giá trị của các trường trong các văn bản của một bộ sưu tập. Cấu trúc dữ liệu này được sử dụng trong tối ưu truy vấn MongoDB để sắp xếp nhanh các văn bản trong một bộ sưu tập.
Chúng ta có thể khởi tạo chỉ mục bằng cách gọi hàm ensureIndex() và cung cấp một văn bản với một hoặc nhiều khóa để đánh chỉ mục. Ví dụ đánh chỉ mục cho trường name trong students
db.students.ensureIndex({name:1});
Hàm ensureIndex() chỉ khởi tạo chỉ mục nếu nó chưa tồn tại. Để kiểm tra việc tồn tại chỉ mục trên bộ sưu tập students, ta có thể chạy hàm db.students.getIndexes().
Khi một bộ sưu tập được đánh chỉ mục trên một khóa nào đó, truy cập ngẫu nhiên trên biểu thức truy vấn có chứa khóa đó sẽ được thực hiện rất nhanh. Nếu không được đánh chỉ mục, MongoDB phải soát tất cả các văn bản để kiểm tra giá trị của khóa đó trong truy vấn.
Chỉ mục mặc định
Một chỉ mục luôn luôn được tạo ra là _id. Chỉ mục này là đặc biệt và không thể bị xóa và chỉ mục _id là duy nhất cho các khóa của nó.
Các khóa nhúng
Với MongoDB chúng ta thậm chí có thể đánh chỉ mục trên các khóa bên trong văn bản nhúng. Ví dụ
db.students.ensureIndex({“address.city”: 1})
Văn bản như là khóa các trường được đánh chỉ mục có thể là bất kỳ loại nào, bao gồm cả văn bản.
Mảng khi giá trị của trường được đánh chỉ mục của văn bản là một mảng. MongoDB đánh chỉ mục mỗi phần tử của mảng đó.
Chỉ mục hỗn hợp các khóa
Ngoài chỉ mục khóa đơn, MongoDB còn hỗ trợ đánh chỉ mục hỗn hợp nhiều khóa. Giống như đánh chỉ mục cơ bản, chúng ta sử dụng hàm ensureIndex() để khởi tạo chỉ mục.
db.things.ensureIndex({j:1, name:-1});
Khi khởi tạo một chỉ mục, số đi cùng với khóa là hướng của chỉ mục, 1 tăng dần, -1 giảm dần. Hướng không ảnh hưởng đến việc truy cập ngẫu nhiên nhưng quan trọng nếu bạn đang làm các truy vấn sắp xếp hoặc phân loại trên chỉ mục hỗn hợp.
Nếu chúng ta có một chỉ mục hỗn hợp trên nhiều trường, chúng ta có thể sử dụng nó để truy vấn trên các tập hợp con đầu của các trường đó. Ví dụ ta có chỉ mục trên (a, b, c), ta có thể sử dụng nó để truy vấn trên (a), (a, b), (a, b, c).
Chỉ mục thưa thớt
Chỉ mục thưa thớt là chỉ mục mà chỉ bao gồm các văn bản có trường được đánh chỉ mục. Bất kỳ văn bản nào bị thiếu trường đánh chỉ mục thưa thớt đều không được lưu vào trong chỉ mục. Các chỉ mục là thưa thớt vì bị thiếu những văn bản không có giá trị của trường được đánh chỉ mục.
Chỉ mục thưa thớt, theo định nghĩa, là không đầy đủ và hoạt động khác với chỉ mục đầy đủ. Khi sử dụng chỉ mục thưa thớt để sắp xếp, một vài văn bản trong bộ sưu tập sẽ không được trả về. Đó là do chỉ những văn bản được đánh chỉ mục mới được trả về.
db.people.ensureIndex({ title : 1 } , { sparse : true})
db.people.save({name:”Jim”})db.people.save({name:”Sarah”, title:”Princess”})
db.people.find({title:{$ne:null}}).sort({title:1}) // returns only Sarah
Chỉ mục duy nhất
MongoDB hỗ trợ đánh chỉ mục duy nhất, đảm bảo rằng không có văn bảo nào được chèn mà giá trị của khóa được đánh chỉ mục lại trùng với văn bản đã tồn tại. Để tạo ra một chỉ mục đảm bảo ràng không có 2 văn bản có cùng giá trị cho 2 trường firstname và lastname ta làm như sau:db.things.ensureIndex({firstname: 1, lastname: 1}, {unique: true});
Khóa bị thiếu
Khi một văn bản được lưu vào bộ sưu tập với việc đánh chỉ mục duy nhất, bất kỳ khóa được đánh chỉ mục nào bị thiếu sẽ được chèn vào với giá trị null. Vì vậy, không được phép chèn nhiều văn bản bị thiếu cùng một khóa được đánh chỉ mục
db.things.ensureIndex({firstname: 1}, {unique: true});
db.things.save({lastname: “Smith”});
//Next operation will fail because of the unique index on firstname.db.things.save({lastname: “Jones”});
Giá trị lặp lại
Chỉ mục duy nhất không cho phép một khóa có giá trị nhân bản. Nếu bạn muốn đánh chỉ mục bằng mọi giá, hãy giữ văn bản đầu tiên trong CSDL và xóa tất cả các văn bản có giá trị bị nhân bản, thêm tùy chọn dropDups.
db.things.ensureIndex({firstname : 1}, {unique : true, dropDups : true})
Xóa chỉ mục
Xóa tất cả các chỉ mục trên bộ sưu tập: db.collection.dropIndexes();
Xóa chỉ mục đơn: db.collection.dropIndex({x: 1, y: -1})
Chạy trực tiếp như một lệnh mà không cần hỗ trợ:
// note: command was “deleteIndexes”, not “dropIndexes”, before MongoDB v1.3.2
// remove index with key pattern {y:1} from collection foodb.runCommand({dropIndexes:’foo’, index : {y:1}})
// remove all indexes:db.runCommand({dropIndexes:’foo’, index : ‘*’})
ReIndex
Lệnh reIndex sẽ xây dựng lại tất cả các chỉ mục cho bộ sưu tập.
db.myCollection.reIndex ()/ / giống như: db.runCommand ({reIndex: ‘myCollection’})
Thông thường, điều này là không cần thiết. Chúng ta có thể làm điều này nếu kích thước bộ sưu tập đã thay đổi đáng kể hoặc không gian đĩa được sử dụng bởi các chỉ mục có vẻ lớn bất thường.
reIndex sẽ bị chậm với các bộ sưu tập lớn.
Các lệnh sửa CSDL tái tạo lại tất cả các chỉ mục trong CSDL.
Lưu ý
- Chỉ mục trong MongoDB phân biệt chữ hoa chữ thường
- Chỉ sốthông tinđược lưu giữtrongbộ sưu tậpindexes, chạydb.system.indexes.find()đểxemví dụ.
Hiệu suất chỉ mục
Việc đánh chỉ mục thực hiện rất nhanh. Cập nhật được thực hiện nhanh hơn vì MongoDB có thể tìm thấy các văn bản cần cập nhật rất nhanh chóng. Tuy nhiên, với việc sử dụng chỉ mục, khi ghi dữ liệu vào bộ sưu tập, các khóa sau đó phải được thêm vào trường chỉ mục. Như vậy, chỉ mục chỉ tốt cho bộ sưu tập có số lượng đọc nhiều hơn rất nhiều số lượng ghi.
Đối với các bộ sưu tập chú trọng ghi, việc sử dụng chỉ mục trong một số trường hợp có thể phản tác dụng. Hầu hết các bộ sưu tập đều chú trọng đọc vì vậy, mà chỉ mục là tốt trong hầu hết các tình huống.
Sử dụng Sort() mà không cần chỉ mục
Chúng ta có thể sử dụng sort() để trả về dữ liệu được sắp thứ tự mà không cần phải dùng chỉ mục nếu dữ liệu cần trả về là nhỏ (< 4 MB). Đối với những trường hợp này tốt nhất là sử dụng hàm limit() và sort() cùng nhau.
Sao chép
Có lẽ công việc quan trọng nhất của bất kỳ quản trị viên MongoDB là đảm bảo sao cho sao chép được thiết lập và hoạt động đúng. Sao chép có thể được sử dụng hoàn toàn để dự phòng và toàn vẹn dữ liệu hoặc có thể được sử dụng cho mục đích cao hơn như mở rộng đọc, sao lưu nóng,…
MongoDB hỗ trợ sao chép dữ liệu không đồng bộ giữa các máy chủ. Tại một thời điểm, chỉ có 1 máy chủ hoạt động để ghi (primary hay master).
Có hai hình thức sao chép.
- Master-Slave Replication
- Replica Sets.
Master – Slave Replication
Sao chép Master-slave là mô hình sao chép phổ biến nhất được hỗ trợ bởi MongoDB. Mô hình này rất linh hoạt và có thể được sử dụng để sao lưu, dự phòng, mở rộng đọc, …
Để thiết lập cần khởi động nút master và một hoặc nhiều nút slave, các nút này đều biết địa chỉ của nút master. Để khởi động master, chạy mongod –master. Để khởi động slave, chạy mongod –slave –source master_address, trong đó master_address là địa chỉ của nút master vừa được khởi động
Replica Sets
Replica Sets là một cụm master-slave tự động chịu lỗi. Replica Sets không có một master cố định, một master được bầu chọn và có thể thay đổi đến nút khác nếu master bị sập [1].
Khi server chính chết, server cấp 2 chở thành server chính (hình 1.4).
Nếu server chính ban đầu hoạt động trở lại, nó trở thành server cấp 2
Truy vấn
Một trong những tính năng tốt nhất của MongoDB là hỗ trợ truy vấn động (ad hoc). Hệ thống hỗ trợ truy vấn động không yêu cầu bất cứ chỉ mục nào để tìm dữ liệu. Người dùng có thể tìm dữ liệu với việc sử dụng bất kỳ tiêu chuẩn nào. Với CSDL quan hệ, truy vấn động là chuẩn hóa.
Đối tượng biểu thức truy vấn
MongoDB hỗ trợ một số các đối tượng truy vấn để lấy dữ liệu. Ví dụ, giả sử chúng ta muốn sử dụng trình MongoDB để trả về mọi văn bản trong bộ sưu tập users. Truy vấn sẽ được viết như sau:
db.users.find({})
Trong trường hợp này, lựa chọn (điều kiện) của chúng ta là trống, nó phù hợp với mọi văn bản trong bộ sưu tập. Chúng ta xem thêm một số ví dụ:
db.users.find({‘last_name’: ‘Smith’})
Ở đây, lựa chọn của chúng ta là tất cả các văn bản mà thuộc tính last_name là Smith.
Các tùy chọn truy vấn
Lựa chọn các trường: Ngoài các biểu thức truy vấn, truy vấn MongoDB còn có thể thêm vào các tham số. Ví dụ, chúng ta muốn các số CMT của tất cả người có họ là Smith, ta có thực hiện truy vấn:
// lấy trường ssn của các văn bản có last_name == ‘Smith’:
db.users.find({last_name: ‘Smith’}, {‘ssn’: 1});
// lấy tất cả các trường ngoại trừ trường thumbnail đối với tất cả các văn bản
db.users.find({}, {thumbnail:0});
Chú ý rằng, trường _id luôn luôn được trả về ngay cả khi không yêu cầu
Sắp xếp: Truy vấn MongoDB có thể trả về kết quả được sắp xếp. Để trả về tất cả các văn bản mà trường last_name được sắp xếp theo thứ tự tăng dần, ta viết truy vấn sau:
db.users.find({}).sort({last_name: 1});
Bỏ qua và giới hạn: MongoDB luôn luôn hỗ trợ bỏ qua và giới hạn để phân trang một cách dễ dàng. Ví dụ ta muốn bỏ qua 20 họ đầu tiên và giới hạn kết quả đến 10, ta viết truy vấn sau:
db.users.find().skip(20).limit(10);db.users.find({}, {}, 10, 20); // giống như lệnh trên nhưng không rõ ràng.
SlaveOk: Khi thực hiện truy vấn ở một hoặc nhiều bản sao, trình tiện ích gửi yêu cầu đến master, để thực hiện truy vấn đối với slave, truy vấn có thể chạy với tùy chọn slaveOk.
db.getMongo().setSlaveOk(); // cho phép truy vấn slave
db.users.find(…)
Con trỏ: Các truy vấn CSDL được thực hiện với phương thức find(), với kỹ thuật này một con trỏ được trả về. Con trỏ sau đó được sử dụng lặp đi lặp lại để lấy tất cả các văn bản mà truy vấn trả về. Chúng ta có thể xem ví dụ sau:
> var cur = db.example.find();
> cur.forEach( function(x) { print(tojson(x))});
{“n” : 1 , “_id” : “497ce96f395f2f052a494fd4”}
{“n” : 2 , “_id” : “497ce971395f2f052a494fd5”}
{“n” : 3 , “_id” : “497ce973395f2f052a494fd6”}
>
Như vậy, MongoDB là một CSDL hướng văn bản, lưu trữ dữ liệu dưới cặp khóa/giá trị. Các đối tượng trong MongoDB thường được nhúng trong các đối tượng mức cao hơn để tăng tốc độ xử lý truy vấn. Để tăng tốc độ truy vấn, người ta cũng thường đánh chỉ mục cho những bộ sưu tập có tỉ lệ đọc, ghi cao. MongoDB thực hiện truy vấn để lấy dữ liệu thông qua các biểu thức truy vấn cùng các tham số cần thiết. Với những dự án mà tỉ lệ lượng dữ liệu ghi vào CSDL lớn hơn lượng đọc thì lựa chọn MongoDB sẽ mang lại hiệu quả cao.
Bộ môn Công nghệ thông tin
Trường Cao đẳng FPT Mạng cá cược bóng đá
cơ sở Hà Nội