防止意外删除数据库

本页面介绍了如何防止 Spanner 数据库意外发生 删除。

Spanner 数据库删除保护可防止意外删除 拥有必要角色的用户或服务账号 用于删除数据库的 IAM 权限。启用数据库删除保护后 您可以保护对应用和服务非常重要的数据库。 使用数据库删除保护和时间点恢复备份功能,以提供一整套 数据保护功能。

默认情况下,在您创建新集群时,删除保护设置处于停用状态 数据库。您可以在完成删除后启用删除保护设置, 已成功创建数据库。此外,您还可在 现有数据库如果您要保护多个数据库,请启用此设置 分别对每个数据库运行启用或停用删除保护功能不起作用 对数据库的性能造成任何影响如果您需要删除数据库 则需要先停用该保护 您可以删除数据库

限制

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

  • 正在删除数据库。
  • 是否正在从备份中恢复数据库。(恢复操作之后 即可启用数据库保护)。

此外,数据库备份和从备份恢复的数据库也不会 并沿用其源数据库的数据库删除保护设置。更新后 使用备份恢复数据库时,必须启用其数据库删除功能 单独进行保护

如果您删除项目,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: %w", 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: %w", 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: %w", 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);
    }
  }
}

<ph type="x-smartling-placeholder">

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();

<ph type="x-smartling-placeholder">

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()
    );
}

<ph type="x-smartling-placeholder">

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,
        )
    )

<ph type="x-smartling-placeholder">

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
立即返回,无需等待正在进行的操作 操作完成。

后续步骤