删除集合和子集合

删除 Cloud Firestore 中的数据 - 特别是从资源有限的移动应用删除 - 执行起来很容易出错,原因如下:

  • 没有以原子方式删除集合的操作。
  • 删除文档不会删除其子集合中的文档。
  • 如果文档有动态子集合,就很难确定要删除给定路径中的哪些数据。
  • 要删除包含超过 500 个文档的集合,需要执行多次批量式写入操作,或数百次单独的删除操作。
  • 在很多应用中,不适合向最终用户授予删除整个集合的权限。

幸运的是,您可以编写并运行 Cloud Functions 函数,安全高效地删除整个集合或集合树。

继续阅读之前,请先参阅有关 Cloud Firestore 数据模型的内容。

解决方法:使用 Cloud Functions Callable 函数删除数据

本指南介绍了如何使用 Cloud Functions Callable 函数删除数据。部署此函数后,您可以直接从移动应用或网站对其进行调用,以便以递归方式删除文档和集合。

要部署函数并查看演示,请参阅示例代码

Cloud Functions 函数

下面的 Cloud Functions 函数能够删除集合及其所有后代。

您无需自行实现 Cloud Functions 函数的递归式删除逻辑,相反,您可以利用 Firebase 命令行界面 (CLI) 的 firestore:delete 命令。您可以使用 firebase-tools 软件包将任何 Firebase CLI 函数导入自己的 Node.js 应用。

Firebase CLI 会使用 Cloud Firestore REST API 查找指定路径下的所有文档并逐个删除。 此实现过程无需了解应用的特定数据层次结构,甚至能够找到并删除不属于任何父级集合的“孤立”文档。

Node.js

/**
 * Initiate a recursive delete of documents at a given path.
 *
 * The calling user must be authenticated and have the custom "admin" attribute
 * set to true on the auth token.
 *
 * This delete is NOT an atomic operation and it's possible
 * that it may fail after only deleting some documents.
 *
 * @param {string} data.path the document or collection path to delete.
 */
exports.recursiveDelete = functions
  .runWith({
    timeoutSeconds: 540,
    memory: '2GB'
  })
  .https.onCall((data, context) => {
    // Only allow admin users to execute this function.
    if (!(context.auth && context.auth.token && context.auth.token.admin)) {
      throw new functions.https.HttpsError(
        'permission-denied',
        'Must be an administrative user to initiate delete.'
      );
    }

    const path = data.path;
    console.log(
      `User ${context.auth.uid} has requested to delete path ${path}`
    );

    // Run a recursive delete on the given document or collection path.
    // The 'token' must be set in the functions config, and can be generated
    // at the command line by running 'firebase login:ci'.
    return firebase_tools.firestore
      .delete(path, {
        project: process.env.GCLOUD_PROJECT,
        recursive: true,
        yes: true,
        token: functions.config().fb.token
      })
      .then(() => {
        return {
          path: path
        };
      });
  });

上述 Cloud Functions 函数是作为 Callable 函数实现的,因此您可以像调用本地函数一样,直接从移动应用或网站调用它。

客户端调用

要调用该函数,请从 Firebase SDK 获取对函数的调用,并传递必需参数:

Web

/**
 * Call the 'recursiveDelete' callable function with a path to initiate
 * a server-side delete.
 */
function deleteAtPath(path) {
    var deleteFn = firebase.functions().httpsCallable('recursiveDelete');
    deleteFn({ path: path })
        .then(function(result) {
            logMessage('Delete success: ' + JSON.stringify(result));
        })
        .catch(function(err) {
            logMessage('Delete failed, see console,');
            console.warn(err);
        });
}

通过使用 Cloud Functions Callable 函数的客户端 SDK,用户的身份验证状态和 path 参数会无缝传递给远程函数。 函数执行完以后,客户端将收到包含结果或异常的回调。要了解如何从 Android、iOS 或其他平台调用 Cloud Functions 函数,请参阅相关文档

限制

上述解决方法演示了如何通过 Callable 函数删除集合,不过您应该注意以下限制:

  • 一致性 - 上述代码一次删除一个文档。如果您在删除操作进行时执行查询,最终可能导致部分完成的状态,即只有部分目标文档删除成功。 另外,删除操作无法保证全部删除或全部不删除,因此请做好准备应对仅部分文档删除成功的情况。
  • 超时 - 根据配置,上述函数最多运行 540 秒,超出即会超时。用于执行删除操作的代码每秒钟最多可以删除 4000 个文档。如果您需要删除超过 200 万个文档,则应该考虑在您自己的服务器上运行此操作,以免超时。