Spanner 使用入门 (Node.js)

目标

本教程将介绍如何使用 Node.js 版 Spanner 客户端库完成以下步骤:

  • 创建 Spanner 实例和数据库。
  • 写入、读取数据库中的数据和对数据执行 SQL 查询。
  • 更新数据库架构。
  • 使用读写事务更新数据。
  • 向数据库添加二级索引。
  • 使用索引来读取数据和对数据执行 SQL 查询。
  • 使用只读事务检索数据。

费用

本教程使用 Spanner,它是Google Cloud的收费组件。如需了解使用 Spanner 的费用,请参阅价格

准备工作

完成设置中介绍的步骤,包括创建和设置默认 Google Cloud 项目、启用结算功能、启用 Cloud Spanner API 以及设置 OAuth 2.0 来获取身份验证凭据以使用 Cloud Spanner API。

尤其要确保运行 gcloud auth application-default login,以便使用身份验证凭据设置本地开发环境。

准备本地 Node.js 环境

  1. 按照步骤设置 Node.js 开发环境

  2. 将示例应用代码库克隆到本地机器上:

    git clone https://github.com/googleapis/nodejs-spanner 

    或者,也可以下载该示例的 zip 文件并将其解压缩。

  3. 切换到包含 Spanner 示例代码的目录:

    cd samples/ 
  4. 使用 npm 安装依赖项:

    npm install 

创建实例

在首次使用 Spanner 时,必须创建一个实例,实例是 Spanner 数据库使用的资源分配单位。创建实例时,请选择一个实例配置(决定数据的存储位置),同时选择要使用的节点数(决定实例中服务资源和存储资源的数量)。

如需了解如何使用以下任一方法创建 Spanner 实例,请参阅创建实例。您可以将实例命名为 test-instance,以便将其用于本文档中引用名为 test-instance 的实例的其他主题。

  • Google Cloud CLI
  • Google Cloud 控制台
  • 客户端库(C++、C#、Go、Java、Node.js、PHP、Python 或 Ruby)

浏览示例文件

示例仓库包含一个示例,展示了如何将 Spanner 与 Node.js 搭配使用。

请浏览 samples/schema.js 文件,其中说明了如何创建数据库和修改数据库架构。数据使用架构和数据模型页面中显示的示例架构。

创建数据库

GoogleSQL

node schema.js createDatabase test-instance example-db MY_PROJECT_ID

PostgreSQL

node schema.js createPgDatabase test-instance example-db MY_PROJECT_ID

您应该会看到:

Created database example-db on instance test-instance. 
以下代码会创建一个数据库以及该数据库中的两个表。

GoogleSQL

 /**  * TODO(developer): Uncomment the following lines before running the sample.  */ // const projectId = 'my-project-id'; // const instanceId = 'my-instance'; // const databaseId = 'my-database';  // Imports the Google Cloud client library const {Spanner} = require('@google-cloud/spanner');  // creates a client const spanner = new Spanner({   projectId: projectID, });  const databaseAdminClient = spanner.getDatabaseAdminClient();  const createSingersTableStatement = ` CREATE TABLE Singers (   SingerId    INT64 NOT NULL,   FirstName   STRING(1024),   LastName    STRING(1024),   SingerInfo  BYTES(MAX),   FullName    STRING(2048) AS (ARRAY_TO_STRING([FirstName, LastName], " ")) STORED, ) PRIMARY KEY (SingerId)`; const createAlbumsTableStatement = ` CREATE TABLE Albums (   SingerId    INT64 NOT NULL,   AlbumId     INT64 NOT NULL,   AlbumTitle  STRING(MAX) ) PRIMARY KEY (SingerId, AlbumId), INTERLEAVE IN PARENT Singers ON DELETE CASCADE`;  // Creates a new database try {   const [operation] = await databaseAdminClient.createDatabase({     createStatement: 'CREATE DATABASE `' + databaseID + '`',     extraStatements: [       createSingersTableStatement,       createAlbumsTableStatement,     ],     parent: databaseAdminClient.instancePath(projectID, instanceID),   });    console.log(`Waiting for creation of ${databaseID} to complete...`);   await operation.promise();    console.log(`Created database ${databaseID} on instance ${instanceID}.`); } catch (err) {   console.error('ERROR:', err); } 

PostgreSQL

/**  * 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 createPgDatabase() {   // Creates a PostgreSQL database. PostgreSQL create requests may not contain any additional   // DDL statements. We need to execute these separately after the database has been created.   const [operationCreate] = await databaseAdminClient.createDatabase({     createStatement: 'CREATE DATABASE "' + databaseId + '"',     parent: databaseAdminClient.instancePath(projectId, instanceId),     databaseDialect:       protos.google.spanner.admin.database.v1.DatabaseDialect.POSTGRESQL,   });    console.log(`Waiting for operation on ${databaseId} to complete...`);   await operationCreate.promise();   const [metadata] = await databaseAdminClient.getDatabase({     name: databaseAdminClient.databasePath(projectId, instanceId, databaseId),   });   console.log(     `Created database ${databaseId} on instance ${instanceId} with dialect ${metadata.databaseDialect}.`,   );    // Create a couple of tables using a separate request. We must use PostgreSQL style DDL as the   // database has been created with the PostgreSQL dialect.   const statements = [     `CREATE TABLE Singers        (SingerId   bigint NOT NULL,       FirstName   varchar(1024),       LastName    varchar(1024),       SingerInfo  bytea,       FullName    character varying(2048) GENERATED ALWAYS AS (FirstName || ' ' || LastName) STORED,       PRIMARY KEY (SingerId)       );       CREATE TABLE Albums        (AlbumId    bigint NOT NULL,       SingerId    bigint NOT NULL REFERENCES Singers (SingerId),       AlbumTitle  text,       PRIMARY KEY (AlbumId)       );`,   ];   const [operationUpdateDDL] = await databaseAdminClient.updateDatabaseDdl({     database: databaseAdminClient.databasePath(       projectId,       instanceId,       databaseId,     ),     statements: [statements],   });   await operationUpdateDDL.promise();   console.log('Updated schema'); } createPgDatabase();

下一步是将数据写入数据库。

创建数据库客户端

您必须先创建一个 Database,然后才能执行读写操作:

// Imports the Google Cloud client library const {Spanner} = require('@google-cloud/spanner');  // Creates a client const spanner = new Spanner({projectId});  // Gets a reference to a Cloud Spanner instance and database const instance = spanner.instance(instanceId); const database = instance.database(databaseId);  // The query to execute const query = {   sql: 'SELECT 1', };  // Execute a simple SQL statement const [rows] = await database.run(query); console.log(`Query: ${rows.length} found.`); rows.forEach(row => console.log(row));

您可以将 Database 视为数据库连接:您与 Spanner 的所有交互都必须通过 Database 进行。通常,您可以在应用启动时创建 Database,然后重复使用该 Database 来读取、写入和执行事务。每个客户端均使用 Spanner 中的资源。

如果您在同一应用中创建多个客户端,则应调用 Database.close() 来清理客户端的资源,包括网络连接(一旦不再需要这些资源就立即清理)。

如需了解详情,请参阅 Database 参考文档。

使用 DML 写入数据

您可以在读写事务中使用数据操纵语言 (DML) 插入数据。

使用 runUpdate() 方法来执行 DML 语句。

// Imports the Google Cloud client library const {Spanner} = require('@google-cloud/spanner');  /**  * TODO(developer): Uncomment the following lines before running the sample.  */ // const projectId = 'my-project-id'; // const instanceId = 'my-instance'; // const databaseId = 'my-database';  // Creates a client const spanner = new Spanner({   projectId: projectId, });  // Gets a reference to a Cloud Spanner instance and database const instance = spanner.instance(instanceId); const database = instance.database(databaseId);  database.runTransaction(async (err, transaction) => {   if (err) {     console.error(err);     return;   }   try {     const [rowCount] = await transaction.runUpdate({       sql: `INSERT Singers (SingerId, FirstName, LastName) VALUES       (12, 'Melissa', 'Garcia'),       (13, 'Russell', 'Morales'),       (14, 'Jacqueline', 'Long'),       (15, 'Dylan', 'Shaw')`,     });     console.log(`${rowCount} records inserted.`);     await transaction.commit();   } catch (err) {     console.error('ERROR:', err);   } finally {     // Close the database when finished.     database.close();   } }); 

使用 writeUsingDml 参数运行示例。

node dml.js writeUsingDml test-instance example-db MY_PROJECT_ID

您应该会看到:

4 records inserted. 

使用变更写入数据

您还可以使用变更插入数据。

您可以使用 Table 对象来写入数据。Table.insert() 方法将向表中添加新行。单个批处理中的所有插入均以原子方式应用。

此代码演示了如何使用变更写入数据:

// Imports the Google Cloud client library const {Spanner} = require('@google-cloud/spanner');  /**  * TODO(developer): Uncomment the following lines before running the sample.  */ // const projectId = 'my-project-id'; // const instanceId = 'my-instance'; // const databaseId = 'my-database';  // Creates a client const spanner = new Spanner({   projectId: projectId, });  // Gets a reference to a Cloud Spanner instance and database const instance = spanner.instance(instanceId); const database = instance.database(databaseId);  // Instantiate Spanner table objects const singersTable = database.table('Singers'); const albumsTable = database.table('Albums');  // Inserts rows into the Singers table // Note: Cloud Spanner interprets Node.js numbers as FLOAT64s, so // they must be converted to strings before being inserted as INT64s try {   await singersTable.insert([     {SingerId: '1', FirstName: 'Marc', LastName: 'Richards'},     {SingerId: '2', FirstName: 'Catalina', LastName: 'Smith'},     {SingerId: '3', FirstName: 'Alice', LastName: 'Trentor'},     {SingerId: '4', FirstName: 'Lea', LastName: 'Martin'},     {SingerId: '5', FirstName: 'David', LastName: 'Lomond'},   ]);    await albumsTable.insert([     {SingerId: '1', AlbumId: '1', AlbumTitle: 'Total Junk'},     {SingerId: '1', AlbumId: '2', AlbumTitle: 'Go, Go, Go'},     {SingerId: '2', AlbumId: '1', AlbumTitle: 'Green'},     {SingerId: '2', AlbumId: '2', AlbumTitle: 'Forever Hold your Peace'},     {SingerId: '2', AlbumId: '3', AlbumTitle: 'Terrified'},   ]);    console.log('Inserted data.'); } catch (err) {   console.error('ERROR:', err); } finally {   await database.close(); }

使用 insert 参数运行示例。

node crud.js insert test-instance example-db MY_PROJECT_ID

您应该会看到:

Inserted data. 

使用 SQL 查询数据

Spanner 支持使用 SQL 接口读取数据,您可以使用 Google Cloud CLI 在命令行中访问该接口,也可以使用 Node.js 版 Spanner 客户端库以编程方式访问该接口。

在命令行中

执行以下 SQL 语句,读取 Albums 表中所有列的值:

gcloud spanner databases execute-sql example-db --instance=test-instance \     --sql='SELECT SingerId, AlbumId, AlbumTitle FROM Albums' 

结果会显示以下内容:

SingerId AlbumId AlbumTitle 1        1       Total Junk 1        2       Go, Go, Go 2        1       Green 2        2       Forever Hold Your Peace 2        3       Terrified 

使用 Node.js 版 Spanner 客户端库

除了在命令行中执行 SQL 语句外,还可以使用 Node.js 版 Spanner 客户端库以编程方式发出相同的 SQL 语句。

使用 Database.run() 运行 SQL 查询。

// Imports the Google Cloud client library const {Spanner} = require('@google-cloud/spanner');  /**  * TODO(developer): Uncomment the following lines before running the sample.  */ // const projectId = 'my-project-id'; // const instanceId = 'my-instance'; // const databaseId = 'my-database';  // Creates a client const spanner = new Spanner({   projectId: projectId, });  // Gets a reference to a Cloud Spanner instance and database const instance = spanner.instance(instanceId); const database = instance.database(databaseId);  const query = {   sql: 'SELECT SingerId, AlbumId, AlbumTitle FROM Albums', };  // Queries rows from the Albums table try {   const [rows] = await database.run(query);    rows.forEach(row => {     const json = row.toJSON();     console.log(       `SingerId: ${json.SingerId}, AlbumId: ${json.AlbumId}, AlbumTitle: ${json.AlbumTitle}`,     );   }); } catch (err) {   console.error('ERROR:', err); } finally {   // Close the database when finished.   await database.close(); }

下面演示了如何发出查询并访问数据:

node crud.js query test-instance example-db MY_PROJECT_ID

您应该会看到以下结果:

SingerId: 1, AlbumId: 1, AlbumTitle: Total Junk SingerId: 1, AlbumId: 2, AlbumTitle: Go, Go, Go SingerId: 2, AlbumId: 1, AlbumTitle: Green SingerId: 2, AlbumId: 2, AlbumTitle: Forever Hold your Peace SingerId: 2, AlbumId: 3, AlbumTitle: Terrified 

使用 SQL 参数进行查询

如果您的应用具有频繁执行的查询,您可以通过将其参数化来提高其性能。生成的参数查询可以缓存下来并重复使用,这样做可以降低编译开销。如需了解详情,请参阅使用查询参数来加快频繁执行的查询的运行速度

以下示例演示了如何在 WHERE 子句中使用参数来查询包含特定 LastName 值的记录。

// Imports the Google Cloud client library const {Spanner} = require('@google-cloud/spanner');  /**  * TODO(developer): Uncomment the following lines before running the sample.  */ // const projectId = 'my-project-id'; // const instanceId = 'my-instance'; // const databaseId = 'my-database';  // Creates a client const spanner = new Spanner({   projectId: projectId, });  // Gets a reference to a Cloud Spanner instance and database const instance = spanner.instance(instanceId); const database = instance.database(databaseId);  const query = {   sql: `SELECT SingerId, FirstName, LastName         FROM Singers WHERE LastName = @lastName`,   params: {     lastName: 'Garcia',   }, };  // Queries rows from the Albums table try {   const [rows] = await database.run(query);    rows.forEach(row => {     const json = row.toJSON();     console.log(       `SingerId: ${json.SingerId}, FirstName: ${json.FirstName}, LastName: ${json.LastName}`,     );   }); } catch (err) {   console.error('ERROR:', err); } finally {   // Close the database when finished.   database.close(); }

下面演示了如何发出查询并访问数据:

node dml.js queryWithParameter test-instance example-db MY_PROJECT_ID 

您应该会看到以下结果:

SingerId: 12, FirstName: Melissa, LastName: Garcia 

使用读取 API 读取数据

除了 Spanner 的 SQL 接口外,Spanner 还支持读取接口。

使用 Table.read() 从数据库中读取行。使用 KeySet 对象定义要读取的键和键范围的集合。

下面演示了如何读取数据:

// Imports the Google Cloud client library const {Spanner} = require('@google-cloud/spanner');  /**  * TODO(developer): Uncomment the following lines before running the sample.  */ // const projectId = 'my-project-id'; // const instanceId = 'my-instance'; // const databaseId = 'my-database';  // Creates a client const spanner = new Spanner({   projectId: projectId, });  // Gets a reference to a Cloud Spanner instance and database const instance = spanner.instance(instanceId); const database = instance.database(databaseId);  // Reads rows from the Albums table const albumsTable = database.table('Albums');  const query = {   columns: ['SingerId', 'AlbumId', 'AlbumTitle'],   keySet: {     all: true,   }, };  try {   const [rows] = await albumsTable.read(query);    rows.forEach(row => {     const json = row.toJSON();     console.log(       `SingerId: ${json.SingerId}, AlbumId: ${json.AlbumId}, AlbumTitle: ${json.AlbumTitle}`,     );   }); } catch (err) {   console.error('ERROR:', err); } finally {   // Close the database when finished.   await database.close(); }

使用 read 参数运行示例。

node crud.js read test-instance example-db MY_PROJECT_ID

您看到的输出结果应该类似于以下内容:

SingerId: 1, AlbumId: 1, AlbumTitle: Total Junk SingerId: 1, AlbumId: 2, AlbumTitle: Go, Go, Go SingerId: 2, AlbumId: 1, AlbumTitle: Green SingerId: 2, AlbumId: 2, AlbumTitle: Forever Hold your Peace SingerId: 2, AlbumId: 3, AlbumTitle: Terrified 

更新数据库架构

假设您需要将名为 MarketingBudget 的新列添加到 Albums 表。向现有表添加新列需要更新数据库架构。Spanner 支持在数据库继续处理流量的同时,对数据库进行架构更新。架构更新不需要使数据库离线,并且不会锁定整个表或列;在架构更新期间,您可以继续将数据写入数据库。如需详细了解支持的架构更新和架构更改性能,请参阅更新架构

添加列

您可以使用 Google Cloud CLI 在命令行中添加列,也可以使用 Node.js 版 Spanner 客户端库以编程方式添加列。

在命令行中

使用以下 ALTER TABLE 命令向表添加新列:

GoogleSQL

gcloud spanner databases ddl update example-db --instance=test-instance \     --ddl='ALTER TABLE Albums ADD COLUMN MarketingBudget INT64' 

PostgreSQL

gcloud spanner databases ddl update example-db --instance=test-instance \     --ddl='ALTER TABLE Albums ADD COLUMN MarketingBudget BIGINT' 

您应该会看到:

Schema updating...done. 

使用 Node.js 版 Spanner 客户端库

使用 Database.updateSchema 修改架构:

 /**  * TODO(developer): Uncomment the following lines before running the sample.  */ // const projectId = 'my-project-id'; // const instanceId = 'my-instance'; // const databaseId = 'my-database';  // Imports the Google Cloud client library const {Spanner} = require('@google-cloud/spanner');  // creates a client const spanner = new Spanner({   projectId: projectId, });  const databaseAdminClient = spanner.getDatabaseAdminClient();  // Creates a new index in the database try {   const [operation] = await databaseAdminClient.updateDatabaseDdl({     database: databaseAdminClient.databasePath(       projectId,       instanceId,       databaseId,     ),     statements: ['ALTER TABLE Albums ADD COLUMN MarketingBudget INT64'],   });    console.log('Waiting for operation to complete...');   await operation.promise();    console.log('Added the MarketingBudget column.'); } catch (err) {   console.error('ERROR:', err); } finally {   // Close the spanner client when finished.   // The databaseAdminClient does not require explicit closure. The closure of the Spanner client will automatically close the databaseAdminClient.   spanner.close(); } 

使用 addColumn 参数运行示例。

node schema.js addColumn test-instance example-db MY_PROJECT_ID

您应该会看到:

Added the MarketingBudget column. 

将数据写入新列

以下代码可将数据写入新列。对于 Albums(1, 1) 键控的行,该代码会将 MarketingBudget 设置为 100000;而对于 Albums(2, 2) 键控的行,该代码会将其设置为 500000

// Imports the Google Cloud client library const {Spanner} = require('@google-cloud/spanner');  /**  * TODO(developer): Uncomment the following lines before running the sample.  */ // const projectId = 'my-project-id'; // const instanceId = 'my-instance'; // const databaseId = 'my-database';  // Creates a client const spanner = new Spanner({   projectId: projectId, });  // Gets a reference to a Cloud Spanner instance and database const instance = spanner.instance(instanceId); const database = instance.database(databaseId);  // Update a row in the Albums table // Note: Cloud Spanner interprets Node.js numbers as FLOAT64s, so they // must be converted to strings before being inserted as INT64s const albumsTable = database.table('Albums');  try {   await albumsTable.update([     {SingerId: '1', AlbumId: '1', MarketingBudget: '100000'},     {SingerId: '2', AlbumId: '2', MarketingBudget: '500000'},   ]);   console.log('Updated data.'); } catch (err) {   console.error('ERROR:', err); } finally {   // Close the database when finished.   database.close(); }

使用 update 参数运行示例。

node crud.js update test-instance example-db MY_PROJECT_ID

您应该会看到:

Updated data. 

您还可以执行 SQL 查询或读取调用来获取刚才写入的值。

以下是执行查询的代码:

// This sample uses the `MarketingBudget` column. You can add the column // by running the `add_column` sample or by running this DDL statement against // your database: //    ALTER TABLE Albums ADD COLUMN MarketingBudget INT64  // Imports the Google Cloud client library const {Spanner} = require('@google-cloud/spanner');  /**  * TODO(developer): Uncomment the following lines before running the sample.  */ // const projectId = 'my-project-id'; // const instanceId = 'my-instance'; // const databaseId = 'my-database';  // Creates a client const spanner = new Spanner({   projectId: projectId, });  // Gets a reference to a Cloud Spanner instance and database const instance = spanner.instance(instanceId); const database = instance.database(databaseId);  const query = {   sql: 'SELECT SingerId, AlbumId, MarketingBudget FROM Albums', };  // Queries rows from the Albums table try {   const [rows] = await database.run(query);    rows.forEach(async row => {     const json = row.toJSON();      console.log(       `SingerId: ${json.SingerId}, AlbumId: ${         json.AlbumId       }, MarketingBudget: ${         json.MarketingBudget ? json.MarketingBudget : null       }`,     );   }); } catch (err) {   console.error('ERROR:', err); } finally {   // Close the database when finished.   database.close(); }

如需执行此查询,请使用 queryNewColumn 参数运行示例。

node schema.js queryNewColumn test-instance example-db MY_PROJECT_ID

您应该会看到:

SingerId: 1, AlbumId: 1, MarketingBudget: 100000 SingerId: 1, AlbumId: 2, MarketingBudget: null SingerId: 2, AlbumId: 1, MarketingBudget: null SingerId: 2, AlbumId: 2, MarketingBudget: 500000 SingerId: 2, AlbumId: 3, MarketingBudget: null 

更新数据

您可以在读写事务中使用 DML 来更新数据。

使用 runUpdate() 方法来执行 DML 语句。

// This sample transfers 200,000 from the MarketingBudget field // of the second Album to the first Album, as long as the second // Album has enough money in its budget. Make sure to run the // addColumn and updateData samples first (in that order).  // Imports the Google Cloud client library const {Spanner} = require('@google-cloud/spanner');  /**  * TODO(developer): Uncomment the following lines before running the sample.  */ // const projectId = 'my-project-id'; // const instanceId = 'my-instance'; // const databaseId = 'my-database';  // Creates a client const spanner = new Spanner({   projectId: projectId, });  // Gets a reference to a Cloud Spanner instance and database const instance = spanner.instance(instanceId); const database = instance.database(databaseId);  const transferAmount = 200000;  database.runTransaction((err, transaction) => {   if (err) {     console.error(err);     return;   }   let firstBudget, secondBudget;   const queryOne = `SELECT MarketingBudget FROM Albums     WHERE SingerId = 2 AND AlbumId = 2`;    const queryTwo = `SELECT MarketingBudget FROM Albums   WHERE SingerId = 1 AND AlbumId = 1`;    Promise.all([     // Reads the second album's budget     transaction.run(queryOne).then(results => {       // Gets second album's budget       const rows = results[0].map(row => row.toJSON());       secondBudget = rows[0].MarketingBudget;       console.log(`The second album's marketing budget: ${secondBudget}`);        // Makes sure the second album's budget is large enough       if (secondBudget < transferAmount) {         throw new Error(           `The second album's budget (${secondBudget}) is less than the transfer amount (${transferAmount}).`,         );       }     }),      // Reads the first album's budget     transaction.run(queryTwo).then(results => {       // Gets first album's budget       const rows = results[0].map(row => row.toJSON());       firstBudget = rows[0].MarketingBudget;       console.log(`The first album's marketing budget: ${firstBudget}`);     }),   ])     .then(() => {       // Transfers the budgets between the albums       console.log(firstBudget, secondBudget);       firstBudget += transferAmount;       secondBudget -= transferAmount;        console.log(firstBudget, secondBudget);        // Updates the database       // Note: Cloud Spanner interprets Node.js numbers as FLOAT64s, so they       // must be converted (back) to strings before being inserted as INT64s.        return transaction         .runUpdate({           sql: `UPDATE Albums SET MarketingBudget = @Budget               WHERE SingerId = 1 and AlbumId = 1`,           params: {             Budget: firstBudget,           },         })         .then(() =>           transaction.runUpdate({             sql: `UPDATE Albums SET MarketingBudget = @Budget                   WHERE SingerId = 2 and AlbumId = 2`,             params: {               Budget: secondBudget,             },           }),         );     })     .then(() => {       // Commits the transaction and send the changes to the database       return transaction.commit();     })     .then(() => {       console.log(         `Successfully executed read-write transaction using DML to transfer ${transferAmount} from Album 2 to Album 1.`,       );     })     .then(() => {       // Closes the database when finished       database.close();     }); });

使用 writeWithTransactionUsingDml 参数运行示例。

node dml.js writeWithTransactionUsingDml test-instance example-db MY_PROJECT_ID 

您应该会看到:

Successfully executed read-write transaction using DML to transfer $200000 from Album 2 to Album 1. 

使用二级索引

假设您想要提取 Albums 表中 AlbumTitle 值在特定范围内的所有行。您可以使用 SQL 语句或读取调用读取 AlbumTitle 列中的所有值,然后舍弃不符合条件的行。不过,执行全表扫描费用高昂,特别是对内含大量行的表来说更是如此。相反,如果对表创建二级索引,按非主键列进行搜索,则可以提高行检索速度。

向现有表添加二级索引需要更新架构。与其他架构更新一样,Spanner 支持在数据库继续处理流量的同时添加索引。Spanner 会使用您的现有数据自动回填索引。回填可能需要几分钟时间才能完成,但在此过程中,您无需使数据库离线,也无需避免写入已编入索引的表。如需了解详情,请参阅添加二级索引

添加二级索引后,Spanner 会自动使用该索引进行 SQL 查询,使用该索引后,查询运行速度可能会提高。如果使用读取接口,则必须指定要使用的索引。

添加二级索引

您可以使用 gcloud CLI 在命令行中添加索引,也可以使用 Node.js 版 Spanner 客户端库以编程方式添加索引。

在命令行中

使用以下 CREATE INDEX 命令向数据库添加索引:

gcloud spanner databases ddl update example-db --instance=test-instance \     --ddl='CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)' 

您应该会看到:

Schema updating...done. 

使用 Node.js 版 Spanner 客户端库

使用 Database.updateSchema() 添加索引:

// Imports the Google Cloud client library const {Spanner} = require('@google-cloud/spanner');  /**  * TODO(developer): Uncomment the following lines before running the sample.  */ // const projectId = 'my-project-id'; // const instanceId = 'my-instance'; // const databaseId = 'my-database';  // Creates a client const spanner = new Spanner({   projectId: projectId, });  const databaseAdminClient = spanner.getDatabaseAdminClient();  const request = ['CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)'];  // Creates a new index in the database try {   const [operation] = await databaseAdminClient.updateDatabaseDdl({     database: databaseAdminClient.databasePath(       projectId,       instanceId,       databaseId,     ),     statements: request,   });    console.log('Waiting for operation to complete...');   await operation.promise();    console.log('Added the AlbumsByAlbumTitle index.'); } catch (err) {   console.error('ERROR:', err); } finally {   // Close the spanner client when finished.   // The databaseAdminClient does not require explicit closure. The closure of the Spanner client will automatically close the databaseAdminClient.   spanner.close(); }

使用 createIndex 参数运行示例。

node indexing.js createIndex test-instance example-db MY_PROJECT_ID

添加索引可能需要几分钟时间。添加索引后,您应该会看到:

Added the AlbumsByAlbumTitle index. 

使用索引进行读取

对于 SQL 查询,Spanner 会自动使用适当的索引。在读取接口中,您必须在请求中指定索引。

要在读取接口中使用索引,请使用 Table.read() 方法。

// Imports the Google Cloud client library const {Spanner} = require('@google-cloud/spanner');  /**  * TODO(developer): Uncomment the following lines before running the sample.  */ // const projectId = 'my-project-id'; // const instanceId = 'my-instance'; // const databaseId = 'my-database';  // Creates a client const spanner = new Spanner({   projectId: projectId, });  // Gets a reference to a Cloud Spanner instance and database const instance = spanner.instance(instanceId); const database = instance.database(databaseId);  const albumsTable = database.table('Albums');  const query = {   columns: ['AlbumId', 'AlbumTitle'],   keySet: {     all: true,   },   index: 'AlbumsByAlbumTitle', };  // Reads the Albums table using an index try {   const [rows] = await albumsTable.read(query);    rows.forEach(row => {     const json = row.toJSON();     console.log(`AlbumId: ${json.AlbumId}, AlbumTitle: ${json.AlbumTitle}`);   }); } catch (err) {   console.error('ERROR:', err); } finally {   // Close the database when finished.   database.close(); }

使用 readIndex 参数运行示例。

node indexing.js readIndex test-instance example-db MY_PROJECT_ID

您应该会看到:

AlbumId: 2, AlbumTitle: Forever Hold your Peace AlbumId: 2, AlbumTitle: Go, Go, Go AlbumId: 1, AlbumTitle: Green AlbumId: 3, AlbumTitle: Terrified AlbumId: 1, AlbumTitle: Total Junk 

添加索引以进行纯索引读取

您可能已经注意到,上面的读取示例不包括读取 MarketingBudget 列。这是因为,Spanner 的读取接口不支持将索引与数据表联接来查找未存储在索引中的值。

请创建 AlbumsByAlbumTitle 的备用定义,用于将 MarketingBudget 的副本存储到索引中。

在命令行中

GoogleSQL

gcloud spanner databases ddl update example-db --instance=test-instance \     --ddl='CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) STORING (MarketingBudget) 

PostgreSQL

gcloud spanner databases ddl update example-db --instance=test-instance \     --ddl='CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) INCLUDE (MarketingBudget) 

添加索引可能需要几分钟时间。添加索引后,您应该会看到:

Schema updating...done. 

使用 Node.js 版 Spanner 客户端库

使用 Database.updateSchema() 添加带 STORING 子句的索引:

// "Storing" indexes store copies of the columns they index // This speeds up queries, but takes more space compared to normal indexes // See the link below for more information: // https://cloud.google.com/spanner/docs/secondary-indexes#storing_clause  // Imports the Google Cloud client library const {Spanner} = require('@google-cloud/spanner');  /**  * TODO(developer): Uncomment the following lines before running the sample.  */ // const projectId = 'my-project-id'; // const instanceId = 'my-instance'; // const databaseId = 'my-database';  // Creates a client const spanner = new Spanner({   projectId: projectId, });  const databaseAdminClient = spanner.getDatabaseAdminClient();  const request = [   'CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) STORING (MarketingBudget)', ];  // Creates a new index in the database try {   const [operation] = await databaseAdminClient.updateDatabaseDdl({     database: databaseAdminClient.databasePath(       projectId,       instanceId,       databaseId,     ),     statements: request,   });    console.log('Waiting for operation to complete...');   await operation.promise();    console.log('Added the AlbumsByAlbumTitle2 index.'); } catch (err) {   console.error('ERROR:', err); } finally {   // Close the spanner client when finished.   // The databaseAdminClient does not require explicit closure. The closure of the Spanner client will automatically close the databaseAdminClient.   spanner.close(); }

使用 createStoringIndex 参数运行示例。

node indexing.js createStoringIndex test-instance example-db MY_PROJECT_ID

您应该会看到:

Added the AlbumsByAlbumTitle2 index. 

现在,当您执行读取操作时便可从 AlbumsByAlbumTitle2 索引中提取所有 AlbumIdAlbumTitleMarketingBudget 列:

// "Storing" indexes store copies of the columns they index // This speeds up queries, but takes more space compared to normal indexes // See the link below for more information: // https://cloud.google.com/spanner/docs/secondary-indexes#storing_clause  // Imports the Google Cloud client library const {Spanner} = require('@google-cloud/spanner');  /**  * TODO(developer): Uncomment the following lines before running the sample.  */ // const projectId = 'my-project-id'; // const instanceId = 'my-instance'; // const databaseId = 'my-database';  // Creates a client const spanner = new Spanner({   projectId: projectId, });  // Gets a reference to a Cloud Spanner instance and database const instance = spanner.instance(instanceId); const database = instance.database(databaseId);  const albumsTable = database.table('Albums');  const query = {   columns: ['AlbumId', 'AlbumTitle', 'MarketingBudget'],   keySet: {     all: true,   },   index: 'AlbumsByAlbumTitle2', };  // Reads the Albums table using a storing index try {   const [rows] = await albumsTable.read(query);    rows.forEach(row => {     const json = row.toJSON();     let rowString = `AlbumId: ${json.AlbumId}`;     rowString += `, AlbumTitle: ${json.AlbumTitle}`;     if (json.MarketingBudget) {       rowString += `, MarketingBudget: ${json.MarketingBudget}`;     }     console.log(rowString);   }); } catch (err) {   console.error('ERROR:', err); } finally {   // Close the database when finished.   database.close(); }

使用 readStoringIndex 参数运行示例。

node indexing.js readStoringIndex test-instance example-db MY_PROJECT_ID

您看到的输出结果应该类似于以下内容:

AlbumId: 2, AlbumTitle: Forever Hold your Peace, MarketingBudget: 300000 AlbumId: 2, AlbumTitle: Go, Go, Go, MarketingBudget: null AlbumId: 1, AlbumTitle: Green, MarketingBudget: null AlbumId: 3, AlbumTitle: Terrified, MarketingBudget: null AlbumId: 1, AlbumTitle: Total Junk, MarketingBudget: 300000 

使用只读事务检索数据

假设您要在同一时间戳执行多个读取操作。只读事务会观察事务提交记录的一致前缀,以便应用始终获得一致的数据。 使用 Database.runTransaction() 执行只读事务。

下面演示了如何运行查询并在同一只读事务中执行读取操作:

// Imports the Google Cloud client library const {Spanner} = require('@google-cloud/spanner');  /**  * TODO(developer): Uncomment the following lines before running the sample.  */ // const projectId = 'my-project-id'; // const instanceId = 'my-instance'; // const databaseId = 'my-database';  // Creates a client const spanner = new Spanner({   projectId: projectId, });  // Gets a reference to a Cloud Spanner instance and database const instance = spanner.instance(instanceId); const database = instance.database(databaseId);  // Gets a transaction object that captures the database state // at a specific point in time database.getSnapshot(async (err, transaction) => {   if (err) {     console.error(err);     return;   }   const queryOne = 'SELECT SingerId, AlbumId, AlbumTitle FROM Albums';    try {     // Read #1, using SQL     const [qOneRows] = await transaction.run(queryOne);      qOneRows.forEach(row => {       const json = row.toJSON();       console.log(         `SingerId: ${json.SingerId}, AlbumId: ${json.AlbumId}, AlbumTitle: ${json.AlbumTitle}`,       );     });      const queryTwo = {       columns: ['SingerId', 'AlbumId', 'AlbumTitle'],     };      // Read #2, using the `read` method. Even if changes occur     // in-between the reads, the transaction ensures that both     // return the same data.     const [qTwoRows] = await transaction.read('Albums', queryTwo);      qTwoRows.forEach(row => {       const json = row.toJSON();       console.log(         `SingerId: ${json.SingerId}, AlbumId: ${json.AlbumId}, AlbumTitle: ${json.AlbumTitle}`,       );     });      console.log('Successfully executed read-only transaction.');   } catch (err) {     console.error('ERROR:', err);   } finally {     transaction.end();     // Close the database when finished.     await database.close();   } });

使用 readOnly 参数运行示例。

node transaction.js readOnly test-instance example-db MY_PROJECT_ID

您看到的输出结果应该类似于以下内容:

SingerId: 2, AlbumId: 2, AlbumTitle: Forever Hold your Peace SingerId: 1, AlbumId: 2, AlbumTitle: Go, Go, Go SingerId: 2, AlbumId: 1, AlbumTitle: Green SingerId: 2, AlbumId: 3, AlbumTitle: Terrified SingerId: 1, AlbumId: 1, AlbumTitle: Total Junk SingerId: 1, AlbumId: 2, AlbumTitle: Go, Go, Go SingerId: 1, AlbumId: 1, AlbumTitle: Total Junk SingerId: 2, AlbumId: 1, AlbumTitle: Green SingerId: 2, AlbumId: 2, AlbumTitle: Forever Hold your Peace SingerId: 2, AlbumId: 3, AlbumTitle: Terrified Successfully executed read-only transaction. 

清理

为避免因本教程中使用的资源导致您的 Google Cloud 账号产生额外费用,请删除数据库和您创建的实例。

删除数据库

如果您删除一个实例,则该实例中的所有数据库都会自动删除。 本步骤演示了如何在不删除实例的情况下删除数据库(您仍需为该实例付费)。

在命令行中

gcloud spanner databases delete example-db --instance=test-instance 

使用 Google Cloud 控制台

  1. 前往 Google Cloud 控制台中的 Spanner 实例页面。

    转到“实例”页面

  2. 点击实例。

  3. 点击您想删除的数据库。

  4. 数据库详细信息页面中,点击删除

  5. 确认您要删除数据库并点击删除

删除实例

删除实例会自动删除在该实例中创建的所有数据库。

在命令行中

gcloud spanner instances delete test-instance 

使用 Google Cloud 控制台

  1. 前往 Google Cloud 控制台中的 Spanner 实例页面。

    转到“实例”页面

  2. 点击您的实例。

  3. 点击删除

  4. 确认您要删除实例并点击删除

后续步骤