防止意外删除数据库

本页面介绍如何保护 Spanner 数据库免遭意外删除。

Spanner 数据库删除保护可防止拥有删除数据库所需的 IAM 权限的用户或服务帐号意外删除现有数据库。通过启用数据库删除防护,您可以保护对您的应用和服务至关重要的数据库。将数据库删除保护与时间点恢复备份功能结合使用,可为 Spanner 数据库提供一套全面的数据保护功能。

默认情况下,创建新数据库时,删除保护设置处于停用状态。您可以在数据库创建成功后启用删除保护设置。此外,您还可以对现有数据库启用此设置。如果要保护多个数据库,请分别为每个数据库启用此设置。启用或停用删除保护不会对数据库产生任何性能影响。如果您需要删除启用了数据库保护的数据库,需要先停用该保护,然后才能删除该数据库。

限制

在以下情况下,您无法启用数据库删除保护:

  • 是否删除数据库。
  • 是否使用备份恢复数据库。(恢复操作完成后,您可以启用数据库保护功能)。

此外,数据库的备份和通过备份恢复的数据库不会继承其源数据库的数据库删除保护设置。使用备份恢复数据库后,必须单独启用其数据库删除保护。

如果您删除项目,Spanner 数据库删除保护功能不会阻止删除数据库或实例。如需详细了解删除项目后会发生的情况,请参阅关停(删除)项目

使用 IAM 控制访问权限

如需启用数据库的删除保护设置,您必须具有特定的 IAM 权限

您需要拥有 spanner.databases.update 权限才能启用或停用数据库删除保护。如果您只需要查看数据库配置的状态,则需要具有 spanner.databases.listspanner.databases.get 权限。如需了解如何授予 Spanner IAM 权限,请参阅应用 IAM 权限

如果您拥有数据库的预定义 Spanner Database Admin roles/spanner.databaseAdmin 角色,则可以更新并启用数据库删除保护。

您可以对现有数据库启用数据库删除保护设置,以防止意外删除数据库。

启用数据库删除保护

您可以使用 gcloud CLI、客户端库以及 RESTRPC API 来启用数据库删除防护。您无法使用 Google Cloud 控制台启用数据库删除保护。

gcloud

如需启用数据库的删除保护设置,请运行以下命令:

  gcloud spanner databases update
  DATABASE_ID --instance=INSTANCE_ID
  --enable-drop-protection [--async]

以下选项为必需:

DATABASE_ID
数据库的 ID。
INSTANCE_ID
数据库的实例 ID。

以下选项为可选:

--async
立即返回,无需等待操作完成。

客户端库

C++

void UpdateDatabase(google::cloud::spanner_admin::DatabaseAdminClient client,
                    std::string const& project_id,
                    std::string const& instance_id,
                    std::string const& database_id, bool drop_protection) {
  google::cloud::spanner::Database db(project_id, instance_id, database_id);
  google::spanner::admin::database::v1::Database database;
  database.set_name(db.FullName());
  database.set_enable_drop_protection(drop_protection);
  google::protobuf::FieldMask update_mask;
  update_mask.add_paths("enable_drop_protection");
  auto updated = client.UpdateDatabase(database, update_mask).get();
  if (!updated) throw std::move(updated).status();
  std::cout << "Database " << updated->name() << " successfully updated.\n";
}

C#


using Google.Cloud.Spanner.Admin.Database.V1;
using Google.Cloud.Spanner.Common.V1;
using Google.Protobuf.WellKnownTypes;
using System;
using System.Threading.Tasks;

public class UpdateDatabaseAsyncSample
{
    public async Task<Database> UpdateDatabaseAsync(string projectId, string instanceId, string databaseId)
    {
        var databaseAdminClient = await DatabaseAdminClient.CreateAsync();
        var databaseName = DatabaseName.Format(projectId, instanceId, databaseId);
        Database databaseToUpdate = await databaseAdminClient.GetDatabaseAsync(databaseName);

        databaseToUpdate.EnableDropProtection = true;
        var updateDatabaseRequest = new UpdateDatabaseRequest()
        {
            Database = databaseToUpdate,
            UpdateMask = new FieldMask { Paths = { "enable_drop_protection" } }
        };

        var operation = await databaseAdminClient.UpdateDatabaseAsync(updateDatabaseRequest);

        // Wait until the operation has finished.
        Console.WriteLine("Waiting for the operation to finish.");
        var completedResponse = await operation.PollUntilCompletedAsync();

        if (completedResponse.IsFaulted)
        {
            Console.WriteLine($"Error while updating database {databaseId}: {completedResponse.Exception}");
            throw completedResponse.Exception;
        }

        Console.WriteLine($"Updated database {databaseId}.");

        // Return the updated database.
        return completedResponse.Result;
    }
}

Go

import (
	"context"
	"fmt"
	"io"

	database "cloud.google.com/go/spanner/admin/database/apiv1"
	adminpb "cloud.google.com/go/spanner/admin/database/apiv1/databasepb"
	"google.golang.org/genproto/protobuf/field_mask"
)

func updateDatabase(ctx context.Context, w io.Writer, db string) error {
	// db = `projects/<project>/instances/<instance-id>/database/<database-id>`
	// Instantiate database admin client.
	adminClient, err := database.NewDatabaseAdminClient(ctx)
	if err != nil {
		return fmt.Errorf("updateDatabase.NewDatabaseAdminClient: %v", err)
	}
	defer adminClient.Close()

	// Instantiate the request for performing update database operation.
	op, err := adminClient.UpdateDatabase(ctx, &adminpb.UpdateDatabaseRequest{
		Database: &adminpb.Database{
			Name:                 db,
			EnableDropProtection: true,
		},
		UpdateMask: &field_mask.FieldMask{
			Paths: []string{"enable_drop_protection"},
		},
	})
	if err != nil {
		return fmt.Errorf("updateDatabase.UpdateDatabase: %v", err)
	}

	// Wait for update database operation to complete.
	fmt.Fprintf(w, "Waiting for update database operation to complete [%s]\n", db)
	if _, err := op.Wait(ctx); err != nil {
		return fmt.Errorf("updateDatabase.Wait: %v", err)
	}
	fmt.Fprintf(w, "Updated database [%s]\n", db)
	return nil
}

Java


import com.google.api.gax.longrunning.OperationFuture;
import com.google.cloud.spanner.Spanner;
import com.google.cloud.spanner.SpannerExceptionFactory;
import com.google.cloud.spanner.SpannerOptions;
import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient;
import com.google.common.collect.Lists;
import com.google.protobuf.FieldMask;
import com.google.spanner.admin.database.v1.Database;
import com.google.spanner.admin.database.v1.DatabaseName;
import com.google.spanner.admin.database.v1.UpdateDatabaseMetadata;
import com.google.spanner.admin.database.v1.UpdateDatabaseRequest;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class UpdateDatabaseSample {

  static void updateDatabase() {
    // TODO(developer): Replace these variables before running the sample.
    final String projectId = "my-project";
    final String instanceId = "my-instance";
    final String databaseId = "my-database";

    updateDatabase(projectId, instanceId, databaseId);
  }

  static void updateDatabase(
      String projectId, String instanceId, String databaseId) {
    try (Spanner spanner =
        SpannerOptions.newBuilder().setProjectId(projectId).build().getService();
        DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) {
      final Database database =
          Database.newBuilder()
              .setName(DatabaseName.of(projectId, instanceId, databaseId).toString())
              .setEnableDropProtection(true).build();
      final UpdateDatabaseRequest updateDatabaseRequest =
          UpdateDatabaseRequest.newBuilder()
              .setDatabase(database)
              .setUpdateMask(
                  FieldMask.newBuilder().addAllPaths(
                      Lists.newArrayList("enable_drop_protection")).build())
              .build();
      OperationFuture<Database, UpdateDatabaseMetadata> operation =
          databaseAdminClient.updateDatabaseAsync(updateDatabaseRequest);
      System.out.printf("Waiting for update operation for %s to complete...\n", databaseId);
      Database updatedDb = operation.get(5, TimeUnit.MINUTES);
      System.out.printf("Updated database %s.\n", updatedDb.getName());
    } catch (ExecutionException | TimeoutException e) {
      // If the operation failed during execution, expose the cause.
      throw SpannerExceptionFactory.asSpannerException(e.getCause());
    } catch (InterruptedException e) {
      // Throw when a thread is waiting, sleeping, or otherwise occupied,
      // and the thread is interrupted, either before or during the activity.
      throw SpannerExceptionFactory.propagateInterrupt(e);
    }
  }
}

Node.js

/**
 * TODO(developer): Uncomment these variables before running the sample.
 */
// const instanceId = 'my-instance';
// const databaseId = 'my-database';
// const projectId = 'my-project-id';

// Imports the Google Cloud client library
const {Spanner, protos} = require('@google-cloud/spanner');

// creates a client
const spanner = new Spanner({
  projectId: projectId,
});

const databaseAdminClient = spanner.getDatabaseAdminClient();

async function updateDatabase() {
  // Update the database metadata fields
  try {
    console.log(
      `Updating database ${databaseAdminClient.databasePath(
        projectId,
        instanceId,
        databaseId
      )}.`
    );
    const [operation] = await databaseAdminClient.updateDatabase({
      database: {
        name: databaseAdminClient.databasePath(
          projectId,
          instanceId,
          databaseId
        ),
        enableDropProtection: true,
      },
      // updateMask contains the fields to be updated in database
      updateMask: (protos.google.protobuf.FieldMask = {
        paths: ['enable_drop_protection'],
      }),
    });
    console.log(
      `Waiting for update operation for ${databaseId} to complete...`
    );
    await operation.promise();
    console.log(`Updated database ${databaseId}.`);
  } catch (err) {
    console.log('ERROR:', err);
  }
}
updateDatabase();

PHP

use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient;
use Google\Cloud\Spanner\Admin\Database\V1\Database;
use Google\Cloud\Spanner\Admin\Database\V1\GetDatabaseRequest;
use Google\Cloud\Spanner\Admin\Database\V1\UpdateDatabaseRequest;
use Google\Protobuf\FieldMask;

/**
 * Updates the drop protection setting for a database.
 * Example:
 * ```
 * update_database($projectId, $instanceId, $databaseId);
 * ```
 *
 * @param string $projectId The Google Cloud project ID.
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 */
function update_database(string $projectId, string $instanceId, string $databaseId): void
{
    $newUpdateMaskField = new FieldMask([
        'paths' => ['enable_drop_protection']
    ]);
    $databaseAdminClient = new DatabaseAdminClient();
    $databaseFullName = DatabaseAdminClient::databaseName($projectId, $instanceId, $databaseId);
    $database = (new Database())
        ->setEnableDropProtection(true)
        ->setName($databaseFullName);

    printf('Updating database %s', $databaseId);
    $operation = $databaseAdminClient->updateDatabase((new UpdateDatabaseRequest())
        ->setDatabase($database)
        ->setUpdateMask($newUpdateMaskField));

    $operation->pollUntilComplete();

    $database = $databaseAdminClient->getDatabase(
        new GetDatabaseRequest(['name' => $databaseFullName])
    );
    printf(
        'Updated the drop protection for %s to %s' . PHP_EOL,
        $database->getName(),
        $database->getEnableDropProtection()
    );
}

Python

def update_database(instance_id, database_id):
    """Updates the drop protection setting for a database."""
    from google.cloud.spanner_admin_database_v1.types import \
        spanner_database_admin

    spanner_client = spanner.Client()
    database_admin_api = spanner_client.database_admin_api

    request = spanner_database_admin.UpdateDatabaseRequest(
        database=spanner_database_admin.Database(
            name=database_admin_api.database_path(
                spanner_client.project, instance_id, database_id
            ),
            enable_drop_protection=True,
        ),
        update_mask={"paths": ["enable_drop_protection"]},
    )
    operation = database_admin_api.update_database(request=request)
    print(
        "Waiting for update operation for {}/databases/{} to complete...".format(
            database_admin_api.instance_path(spanner_client.project, instance_id),
            database_id,
        )
    )
    operation.result(OPERATION_TIMEOUT_SECONDS)

    print(
        "Updated database {}/databases/{}.".format(
            database_admin_api.instance_path(spanner_client.project, instance_id),
            database_id,
        )
    )

Ruby

require "google/cloud/spanner/admin/database"

##
# This is a snippet for showcasing how to update database.
#
# @param project_id  [String] The ID of the Google Cloud project.
# @param instance_id [String] The ID of the spanner instance.
# @param database_id [String] The ID of the database.
#
def spanner_update_database project_id:, instance_id:, database_id:
  client = Google::Cloud::Spanner::Admin::Database.database_admin project_id: project_id
  db_path = client.database_path project: project_id, instance: instance_id, database: database_id
  database = client.get_database name: db_path

  puts "Updating database #{database.name}"
  database.enable_drop_protection = true
  job = client.update_database database: database, update_mask: { paths: ["enable_drop_protection"] }
  puts "Waiting for update operation for #{database.name} to complete..."
  job.wait_until_done!
  puts "Updated database #{database.name}"
end

检查数据库是否启用了删除保护

您可以通过查看数据库配置来确定数据库是否启用了删除保护。

gcloud

如需检查数据库是否启用了删除保护,您可以运行 gcloud spanner databases describe 命令来获取数据库的详细信息,也可以运行 gcloud spanner databases list 来获取有关实例中数据库的详细信息。

  gcloud spanner databases describe
  projects/PROJECT_ID/instances/INSTANCE_ID/databases/DATABASE_ID

以下选项为必需:

PROJECT_ID
数据库项目的 ID。
INSTANCE_ID
数据库的实例 ID。
DATABASE_ID
数据库的 ID。

如果启用了删除保护,您将在输出中看到 enableDropProtection: true 参数。

停用数据库删除保护

如果数据库不再需要此保护,或者您需要删除启用了此设置的数据库,可以停用数据库删除保护。

如果要删除具有一个或多个启用了删除保护的数据库的实例,您必须先对该实例中的所有数据库停用删除保护,然后才能删除该实例。

gcloud

如需停用数据库的删除保护设置,请运行以下命令:

  gcloud spanner databases update
  DATABASE_ID --instance=INSTANCE_ID
  --no-enable-drop-protection [--async]

以下选项为必需:

DATABASE_ID
数据库的 ID。
INSTANCE_ID
数据库的实例 ID。

以下选项为可选:

--async
立即返回,无需等待操作完成。

后续步骤