Sửa đổi dữ liệu bằng các thao tác trong quy trình Firestore

Sử dụng các giai đoạn Ngôn ngữ thao tác dữ liệu (DML) update(...) và delete(...) để xây dựng quy trình dữ liệu có thể truy vấn tài liệu, sau đó xoá hoặc sửa đổi dữ liệu.

Yêu cầu đối với ấn bản

Các thao tác được mô tả trên trang này yêu cầu phiên bản Firestore Enterprise.

Trước khi bắt đầu

Bạn nên nắm rõ cách truy vấn cơ sở dữ liệu bằng các thao tác trong quy trình.

Cập nhật tài liệu

Sử dụng giai đoạn DML update(...) để xây dựng quy trình dữ liệu có thể truy vấn tài liệu, sau đó thêm hoặc sửa đổi dữ liệu.

Tất cả các giai đoạn DML phải nằm ở cuối quy trình. Các tài liệu đi vào giai đoạn này phải bao gồm trường __name__ để xác định tài liệu cần cập nhật. Thao tác này sẽ không thành công nếu bất kỳ tài liệu nào bạn cố gắng cập nhật không tồn tại.

Ví dụ: thao tác sau đây sẽ lấp đầy thay đổi về mô hình dữ liệu cho tất cả tài liệu trong một nhóm tập hợp. Quy trình này thêm trường preferences.color vào tất cả tài liệu trong nhóm tập hợp users bị thiếu trường đó.

Node.js
const snapshot = await db.pipeline()
   .collectionGroup("users")
   .where(not(exists(field("preferences.color"))))
   .addFields(constant(null).as("preferences.color"))
   .removeFields("color")
   .update()
   .execute();
Python
from google.cloud.firestore_v1.pipeline_expressions import Constant, Field, Not

snapshot = (
    client.pipeline()
    .collection_group("users")
    .where(Not(Field.of("preferences.color").exists()))
    .add_fields(Constant.of(None).as_("preferences.color"))
    .remove_fields("color")
    .update()
    .execute()
)
Java
Pipeline.Snapshot snapshot = firestore.pipeline()
   .collectionGroup("users")
   .where(not(exists(field("preferences.color"))))
   .addFields(constant((String) null).as("preferences.color"))
   .removeFields("color")
   .update()
   .execute().get();
Bắt đầu
snapshot := client.Pipeline().
	CollectionGroup("users").
	Where(firestore.Not(firestore.FieldExists(firestore.FieldOf("preferences.color")))).
	AddFields(firestore.Selectables(
		firestore.ConstantOfNull().As("preferences.color"),
	)).
	RemoveFields(firestore.Fields("color")).
	Update().
	Execute(ctx)

Xoá tài liệu

Sử dụng giai đoạn DML delete(...) để tạo quy trình dữ liệu có thể truy vấn tài liệu rồi xoá dữ liệu. Để tránh vô tình xoá hàng loạt, các quy trình kết thúc bằng delete(...) phải bao gồm ít nhất một giai đoạn where(...). Tất cả các giai đoạn DML phải nằm ở cuối quy trình.

Ví dụ: quy trình sau đây sẽ xoá tất cả tài liệu users có address.users được đặt thành USA và có __create_time__ dưới 10 ngày:

Node.js
const pipeline = db.pipeline()
  .collectionGroup("users")
  .where(field("address.country").equal("USA"))
  .where(field("__create_time__").timestampAdd("day", 10).lessThan(currentTimestamp()))
  .delete();
await pipeline.execute();
Python
from google.cloud.firestore_v1.pipeline_expressions import CurrentTimestamp, Field

snapshot = (
    client.pipeline()
    .collection_group("users")
    .where(Field.of("address.country").equal("USA"))
    .where(
        Field.of("__create_time__")
        .timestamp_add("day", 10)
        .less_than(CurrentTimestamp())
    )
    .delete()
    .execute()
)
Java
Pipeline.Snapshot deleteResults = firestore.pipeline()
  .collectionGroup("users")
  .where(field("address.country").equal("USA"))
  .where(field("__create_time__").add(constant(10)).lessThan(currentTimestamp()))
  .delete()
  .execute().get();
Bắt đầu
snapshot := client.Pipeline().
	CollectionGroup("users").
	Where(firestore.FieldOf("address.country").Equal("USA")).
	Where(firestore.FieldOf("__create_time__").Add(firestore.ConstantOf(10)).LessThan(firestore.CurrentTimestamp())).
	Delete().
	Execute(ctx)

Chế độ thực thi và tính nhất quán

Bạn có thể thực thi các giai đoạn DML bên trong một giao dịch rõ ràng hoặc bên ngoài một giao dịch:

Thực thi giao dịch

DML giao dịch cung cấp tính nhất quán nguyên tử, tất cả hoặc không có gì trên các tài liệu và hỗ trợ toàn bộ các giai đoạn truy vấn quy trình. Điều này giúp Firestore trở nên lý tưởng cho những khối lượng công việc đòi hỏi sự đảm bảo nghiêm ngặt về tính nhất quán (trong đó không chấp nhận việc cập nhật một phần) hoặc dựa vào các giai đoạn truy vấn phức tạp, miễn là các nội dung sửa đổi nằm trong giới hạn kích thước giao dịch.

Việc chạy quy trình DML bên trong một giao dịch rõ ràng (sử dụng runTransaction/transaction.execute(pipeline) hoặc tuỳ chọn atomic: true) sẽ có hành vi như sau:

  • Tất cả các đột biến đều được cam kết cùng nhau hoặc khôi phục hoàn toàn khi không thành công.
  • Tất cả các giai đoạn truy vấn quy trình đều được hỗ trợ trước giai đoạn DML (bao gồm cả các truy vấn con where, add_fields, union, find_nearest và aggregate).
  • Trong một giao dịch, tất cả thao tác đọc phải thực thi trước mọi thao tác ghi. Vì quy trình DML thực hiện các thao tác ghi, nên bạn không thể thực hiện các thao tác đọc sau khi chạy quy trình DML trong cùng một giao dịch. Ví dụ: bạn không thể gọi một quy trình DML nhiều lần trong một giao dịch, cũng như không thể đọc tài liệu sau khi thực thi một quy trình DML. Việc cố gắng thực thi một lệnh đọc sau khi chạy quy trình DML không thành công và xuất hiện lỗi FAILED_PRECONDITION: Cannot have reads after the first write in a Firestore transaction.
  • Thao tác này phải tuân theo giới hạn giao dịch của phiên bản Firestore Enterprise.

Thực thi không giao dịch

DML không giao dịch được thiết kế để sửa đổi hàng loạt trên một số lượng lớn tài liệu mà không gặp phải giới hạn về kích thước giao dịch. Bằng cách khoá ở cấp tài liệu thay vì trên toàn bộ thao tác, các thao tác đồng thời khác có thể tiếp tục trong khi cập nhật tài liệu trên quy mô lớn.

Khi các giai đoạn DML chạy bên ngoài một giao dịch rõ ràng:

  • Nội dung cập nhật độc lập và thành công một phần: Mỗi tài liệu được cập nhật độc lập với nhau và các thao tác không mang tính nguyên tử trên các tài liệu. Quy trình sẽ dừng lại ở lỗi đầu tiên và các tài liệu đã sửa đổi trước đó vẫn được ghi vào cơ sở dữ liệu, điều này cho phép xảy ra thành công một phần.
  • Hỗ trợ theo giai đoạn:
    • Các giai đoạn nâng cao và nhiều truy vấn (chẳng hạn như aggregate(...), distinct(...), unnest(...), find_nearest(...), union(...), phép nối và truy vấn con) không được hỗ trợ trước DML bên ngoài một giao dịch. Hãy sử dụng một giao dịch nếu quy trình của bạn yêu cầu các giai đoạn này.
  • Giới hạn 20 lượt tra cứu của Quy tắc bảo mật: Mỗi lô tài liệu thực thi trong giao dịch riêng và đánh giá lại các quy tắc bảo mật. Nếu quy tắc bảo mật của bạn sử dụng các lượt tra cứu get() hoặc exists(), thì mỗi lô sẽ thực thi một lượt tra cứu, đạt đến giới hạn 20 lượt tra cứu cho mỗi yêu cầu sau khi ghi khoảng 200 tài liệu và không thành công với PERMISSION_DENIED. Sử dụng Xác nhận tuỳ chỉnh thay vì tra cứu get() hoặc exists() để tránh giới hạn này.

Hạn chế

Hãy lưu ý các giới hạn sau đây đối với các giai đoạn DML:

  • Các giai đoạn DML phải là giai đoạn cuối cùng trong định nghĩa quy trình trước khi gọi .execute().
  • Khi chạy bên ngoài một giao dịch, mỗi tài liệu sẽ được cập nhật độc lập với nhau và có thể thành công một phần.
  • Các thao tác DML không giao dịch tuân theo Quy tắc bảo mật có tra cứu get() hoặc exists() được giới hạn ở khoảng 200 tài liệu do giới hạn 20 lần tra cứu.
  • Các thao tác DML giao dịch bị giới hạn theo hạn mức giao dịch của phiên bản Firestore Enterprise. Vì các giao dịch Firestore không cho phép đọc sau lần ghi đầu tiên, nên bạn không thể gọi một quy trình DML nhiều lần trong một giao dịch, cũng như không thể đọc tài liệu sau khi thực thi một quy trình DML. Việc cố gắng thực thi một lệnh đọc sau khi lệnh ghi DML không thành công sẽ gây ra lỗi FAILED_PRECONDITION: Cannot have reads after the first write in a Firestore transaction.
  • Nếu giai đoạn trước giai đoạn DML tạo ra nhiều tài liệu có cùng __name__ hoặc đánh giá cùng một mã tài liệu đích, thì mỗi đột biến sẽ được xử lý tuần tự:
    • Đối với upsert(...) và update(...): Nhiều đột biến nhắm đến cùng một tài liệu sẽ tuân theo ngữ nghĩa "đột biến cuối cùng sẽ thắng", trong đó đột biến cuối cùng được xử lý sẽ xác định trạng thái cuối cùng của tài liệu.
    • Đối với delete(...): Lần thử đầu tiên sẽ xoá tài liệu và các lần thử tiếp theo cho cùng một tài liệu sẽ không hoạt động.
    • Đối với insert(...): Lần thử đầu tiên sẽ tạo tài liệu và các lần thử tiếp theo nhắm đến cùng một mã tài liệu sẽ không thành công và có lỗi ALREADY_EXISTS.