Introdução ao Spanner no Node.js


Objetivos

Este tutorial explica os seguintes passos através da biblioteca de cliente do Spanner para Node.js:

  • Crie uma instância e uma base de dados do Spanner.
  • Escrever, ler e executar consultas SQL em dados na base de dados.
  • Atualize o esquema da base de dados.
  • Atualize os dados através de uma transação de leitura/escrita.
  • Adicione um índice secundário à base de dados.
  • Use o índice para ler e executar consultas SQL em dados.
  • Recuperar dados através de uma transação só de leitura.

Custos

Este tutorial usa o Spanner, que é um componente faturável do Google Cloud. Para obter informações sobre o custo de utilização do Spanner, consulte a secção Preços.

Antes de começar

Conclua os passos descritos em Configuração, que abrangem a criação e a definição de um projeto Google Cloud predefinido, a ativação da faturação, a ativação da API Cloud Spanner e a configuração do OAuth 2.0 para obter credenciais de autenticação para usar a API Cloud Spanner.

Em particular, certifique-se de que executa gcloud auth application-default login para configurar o seu ambiente de desenvolvimento local com credenciais de autenticação.

Prepare o seu ambiente Node.js local

  1. Siga os passos para configurar um ambiente de programação Node.js

  2. Clone o repositório da app de exemplo para a sua máquina local:

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

    Em alternativa, pode transferir o exemplo como um ficheiro ZIP e extraí-lo.

  3. Altere para o diretório que contém o código de exemplo do Spanner:

    cd samples/
    
  4. Instale as dependências através do npm:

    npm install
    

Crie uma instância

Quando usa o Spanner pela primeira vez, tem de criar uma instância, que é uma atribuição de recursos usados pelas bases de dados do Spanner. Quando cria uma instância, escolhe uma configuração da instância, que determina onde os seus dados são armazenados, bem como o número de nós a usar, o que determina a quantidade de recursos de publicação e armazenamento na sua instância.

Consulte o artigo Crie uma instância para saber como criar uma instância do Spanner através de qualquer um dos seguintes métodos. Pode dar o nome test-instance à sua instância para a usar com outros tópicos neste documento que façam referência a uma instância com o nome test-instance.

  • A CLI do Google Cloud
  • A Google Cloud consola
  • Uma biblioteca cliente (C++, C#, Go, Java, Node.js, PHP, Python ou Ruby)

Explore ficheiros de exemplo

O repositório de exemplos contém um exemplo que mostra como usar o Spanner com o Node.js.

Consulte o ficheiro samples/schema.js, que mostra como criar uma base de dados e modificar um esquema da base de dados. Os dados usam o esquema de exemplo mostrado na página Esquema e modelo de dados.

Crie uma base de dados

GoogleSQL

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

PostgreSQL

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

Deve ver:

Created database example-db on instance test-instance.
O código seguinte cria uma base de dados e duas tabelas na base de dados.

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

O passo seguinte é escrever dados na sua base de dados.

Crie um cliente de base de dados

Antes de poder fazer leituras ou escritas, tem de criar um 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));

Pode pensar num Database como uma ligação de base de dados: todas as suas interações com o Spanner têm de passar por um Database. Normalmente, cria um Database quando a sua aplicação é iniciada e, em seguida, volta a usar esse Database para ler, escrever e executar transações. Cada cliente usa recursos no Spanner.

Se criar vários clientes na mesma app, deve chamar Database.close() para limpar os recursos do cliente, incluindo as ligações de rede, assim que deixar de ser necessário.

Leia mais na Database referência.

Escreva dados com DML

Pode inserir dados através da linguagem de manipulação de dados (DML) numa transação de leitura/escrita.

Usa o método runUpdate() para executar uma declaração 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();
  }
});

Execute o exemplo com o argumento writeUsingDml.

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

Deve ver:

4 records inserted.

Escreva dados com mutações

Também pode inserir dados através de alterações.

Escreve dados através de um objeto Table. O método Table.insert() adiciona novas linhas à tabela. Todas as inserções num único lote são aplicadas de forma atómica.

Este código mostra como escrever os dados através de mutações:

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

Execute o exemplo com o argumento insert.

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

Deve ver:

Inserted data.

Consultar dados através de SQL

O Spanner suporta uma interface SQL para ler dados, à qual pode aceder na linha de comando através da CLI Google Cloud ou programaticamente através da biblioteca de cliente do Spanner para Node.js.

Na linha de comandos

Execute a seguinte declaração SQL para ler os valores de todas as colunas da tabela Albums:

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

O resultado mostra:

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

Use a biblioteca cliente do Spanner para Node.js

Além de executar uma declaração SQL na linha de comandos, pode emitir a mesma declaração SQL de forma programática através da biblioteca de cliente do Spanner para Node.js.

Use Database.run() para executar a consulta 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();
}

Veja como emitir a consulta e aceder aos dados:

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

Deverá ver o seguinte resultado:

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

Consultar através de um parâmetro SQL

Se a sua aplicação tiver uma consulta executada com frequência, pode melhorar o respetivo desempenho parametrizando-a. A consulta paramétrica resultante pode ser colocada em cache e reutilizada, o que reduz os custos de compilação. Para mais informações, consulte o artigo Use parâmetros de consulta para acelerar as consultas executadas com frequência.

Segue-se um exemplo da utilização de um parâmetro na cláusula WHERE para consultar registos que contêm um valor específico para 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();
}

Veja como emitir a consulta e aceder aos dados:

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

Deverá ver o seguinte resultado:

SingerId: 12, FirstName: Melissa, LastName: Garcia

Leia dados através da API de leitura

Além da interface SQL do Spanner, o Spanner também suporta uma interface de leitura.

Use Table.read() para ler linhas da base de dados. Use um objeto KeySet para definir uma coleção de chaves e intervalos de chaves a ler.

Veja como ler os dados:

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

Execute o exemplo com o argumento read.

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

Deverá ver uma saída semelhante à seguinte:

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

Atualize o esquema da base de dados

Suponha que precisa de adicionar uma nova coluna denominada MarketingBudget à tabela Albums. A adição de uma nova coluna a uma tabela existente requer uma atualização ao esquema da base de dados. O Spanner suporta atualizações de esquemas a uma base de dados enquanto a base de dados continua a servir tráfego. As atualizações do esquema não requerem que a base de dados fique offline e não bloqueiam tabelas nem colunas inteiras. Pode continuar a escrever dados na base de dados durante a atualização do esquema. Leia mais acerca das atualizações de esquemas suportadas e do desempenho das alterações de esquemas em Faça atualizações de esquemas.

Adicione uma coluna

Pode adicionar uma coluna na linha de comando através da CLI Google Cloud ou programaticamente através da biblioteca de cliente do Spanner para Node.js.

Na linha de comandos

Use o seguinte comando ALTER TABLE para adicionar a nova coluna à tabela:

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'

Deve ver:

Schema updating...done.

Use a biblioteca cliente do Spanner para Node.js

Use Database.updateSchema para modificar o esquema:


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

Execute o exemplo com o argumento addColumn.

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

Deve ver:

Added the MarketingBudget column.

Escreva dados na nova coluna

O código seguinte escreve dados na nova coluna. Define MarketingBudget como 100000 para a linha identificada por Albums(1, 1) e como 500000 para a linha identificada por Albums(2, 2).

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

Execute o exemplo com o argumento update.

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

Deve ver:

Updated data.

Também pode executar uma consulta SQL ou uma chamada de leitura para obter os valores que acabou de escrever.

Aqui está o código para executar a consulta:

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

Para executar esta consulta, execute o exemplo com o argumento queryNewColumn.

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

Deve ver:

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

Atualize os dados

Pode atualizar dados através de DML numa transação de leitura/escrita.

Usa o método runUpdate() para executar uma declaração 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();
    });
});

Execute o exemplo com o argumento writeWithTransactionUsingDml.

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

Deve ver:

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

Use um índice secundário

Suponhamos que quer obter todas as linhas de Albums que têm valores AlbumTitle num determinado intervalo. Pode ler todos os valores da coluna AlbumTitle através de uma declaração SQL ou de uma chamada de leitura e, em seguida, rejeitar as linhas que não cumprem os critérios, mas esta análise completa da tabela é dispendiosa, especialmente para tabelas com muitas linhas. Em alternativa, pode acelerar a obtenção de linhas quando pesquisa por colunas de chaves não primárias criando um índice secundário na tabela.

A adição de um índice secundário a uma tabela existente requer uma atualização do esquema. Tal como outras atualizações de esquemas, o Spanner suporta a adição de um índice enquanto a base de dados continua a publicar tráfego. O Spanner repreenche automaticamente o índice com os seus dados existentes. Os preenchimentos podem demorar alguns minutos a serem concluídos, mas não tem de colocar a base de dados offline nem evitar escrever na tabela indexada durante este processo. Para mais detalhes, consulte o artigo Adicione um índice secundário.

Depois de adicionar um índice secundário, o Spanner usa-o automaticamente para consultas SQL que provavelmente são executadas mais rapidamente com o índice. Se usar a interface read, tem de especificar o índice que quer usar.

Adicione um índice secundário

Pode adicionar um índice na linha de comandos através da CLI gcloud ou programaticamente através da biblioteca de cliente do Spanner para Node.js.

Na linha de comandos

Use o seguinte comando CREATE INDEX para adicionar um índice à base de dados:

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

Deve ver:

Schema updating...done.

Usar a biblioteca cliente do Spanner para Node.js

Use Database.updateSchema() para adicionar um índice:

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

Execute o exemplo com o argumento createIndex.

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

A adição de um índice pode demorar alguns minutos. Depois de adicionar o índice, deve ver o seguinte:

Added the AlbumsByAlbumTitle index.

Leia através do índice

Para consultas SQL, o Spanner usa automaticamente um índice adequado. Na interface de leitura, tem de especificar o índice no seu pedido.

Para usar o índice na interface de leitura, use o método 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();
}

Execute o exemplo com o argumento readIndex.

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

Deve ver:

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

Adicione um índice para leituras apenas de índice

Pode ter reparado que o exemplo de leitura anterior não inclui a leitura da coluna MarketingBudget. Isto deve-se ao facto de a interface de leitura do Spanner não suportar a capacidade de associar um índice a uma tabela de dados para procurar valores que não estão armazenados no índice.

Crie uma definição alternativa de AlbumsByAlbumTitle que armazene uma cópia de MarketingBudget no índice.

Na linha de comandos

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)

A adição de um índice pode demorar alguns minutos. Depois de adicionar o índice, deve ver o seguinte:

Schema updating...done.

Usar a biblioteca cliente do Spanner para Node.js

Use Database.updateSchema() para adicionar um índice com uma cláusula 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();
}

Execute o exemplo com o argumento createStoringIndex.

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

Deve ver:

Added the AlbumsByAlbumTitle2 index.

Agora, pode executar uma leitura que obtenha todas as colunas AlbumId, AlbumTitle e MarketingBudget do índice AlbumsByAlbumTitle2:

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

Execute o exemplo com o argumento readStoringIndex.

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

Deverá ver uma saída semelhante à seguinte:

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

Obtenha dados através de transações só de leitura

Suponhamos que quer executar mais do que uma leitura na mesma data/hora. As transações de leitura exclusiva observam um prefixo consistente do histórico de confirmações de transações, pelo que a sua aplicação recebe sempre dados consistentes. Use Database.runTransaction() para executar transações só de leitura.

O exemplo seguinte mostra como executar uma consulta e fazer uma leitura na mesma transação só de leitura:

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

Execute o exemplo com o argumento readOnly.

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

Deverá ver uma saída semelhante à seguinte:

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.

Limpeza

Para evitar incorrer em cobranças adicionais na sua conta do Cloud Billing pelos recursos usados neste tutorial, elimine a base de dados e a instância que criou.

Elimine a base de dados

Se eliminar uma instância, todas as bases de dados na mesma são eliminadas automaticamente. Este passo mostra como eliminar uma base de dados sem eliminar uma instância (continua a incorrer em custos pela instância).

Na linha de comandos

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

Usar a Google Cloud consola

  1. Aceda à página Instâncias do Spanner na Google Cloud consola.

    Aceda à página Instâncias

  2. Clique na instância.

  3. Clique na base de dados que quer eliminar.

  4. Na página Detalhes da base de dados, clique em Eliminar.

  5. Confirme que quer eliminar a base de dados e clique em Eliminar.

Elimine a instância

A eliminação de uma instância elimina automaticamente todas as bases de dados criadas nessa instância.

Na linha de comandos

gcloud spanner instances delete test-instance

Usar a Google Cloud consola

  1. Aceda à página Instâncias do Spanner na Google Cloud consola.

    Aceda à página Instâncias

  2. Clique na instância.

  3. Clique em Eliminar.

  4. Confirme que quer eliminar a instância e clique em Eliminar.

O que se segue?