תחילת העבודה עם Spanner ב-Node.js

מטרות

במדריך הזה נסביר איך לבצע את הפעולות הבאות באמצעות ספריית הלקוח של Spanner ל-Node.js:

  • יוצרים מכונה ומסד נתונים ב-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/GoogleCloudPlatform/nodejs-docs-samples.git
    

    אפשר גם להוריד את הדוגמה כקובץ ZIP ולחלץ אותו.

  3. עוברים לספרייה שמכילה את הקוד לדוגמה של Spanner:

    cd nodejs-docs-samples/spanner/
    
  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 או Rust)

עיון בקבצים לדוגמה

מאגר הדוגמאות מכיל דוגמה שמראה איך להשתמש ב-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) בטרנזקציית קריאה-כתיבה.

משתמשים ב-method‏ 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. ה-method‏ 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 או באופן פרוגרמטי באמצעות ספריית הלקוח של Spanner ל-Node.js.

בשורת הפקודה

מריצים את הצהרת ה-SQL הבאה כדי לקרוא את הערכים של כל העמודות מהטבלה Albums:

GoogleSQL

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

תחביר Pipe ב-GoogleSQL

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

בתוצאה רואים:

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

שימוש בספריית הלקוח של Spanner ל-Node.js

בנוסף להרצה של הצהרת SQL בשורת הפקודה, אתם יכולים להנפיק את אותה הצהרת SQL באופן פרוגרמטי באמצעות ספריית הלקוח של Spanner ל-Node.js.

משתמשים ב-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.

GoogleSQL

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

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 Spanner client library
const {Spanner} = require('@google-cloud/spanner');

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

async function queryWithPgParameter() {
  // Gets a reference to a Cloud Spanner instance and database.
  const instance = spanner.instance(instanceId);
  const database = instance.database(databaseId);

  const fieldType = {
    type: 'string',
  };

  const query = {
    sql: `SELECT singerid, firstname, lastname
          FROM singers
          WHERE firstname LIKE $1`,
    params: {
      p1: 'A%',
    },
    types: {
      p1: fieldType,
    },
  };

  // Queries rows from the Singers 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();
  }
}
queryWithPgParameter();

כך מריצים את השאילתה וניגשים לנתונים:

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

התוצאה הבאה אמורה להופיע:

SingerId: 12, FirstName: Melissa, LastName: Garcia

קריאת נתונים באמצעות Read API

בנוסף לממשק ה-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 או באופן פרוגרמטי באמצעות ספריית הלקוח של Spanner ל-Node.js.

בשורת הפקודה

משתמשים בפקודה הבאה של 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.

שימוש בספריית הלקוח של Spanner ל-Node.js

משתמשים ב-Database.updateSchema כדי לשנות את הסכימה:

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

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

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} = require('@google-cloud/spanner');

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

const databaseAdminClient = spanner.getDatabaseAdminClient();

async function pgAddColumn() {
  const request = ['ALTER TABLE Albums ADD COLUMN MarketingBudget BIGINT'];

  // Alter existing table to add a column.
  const [operation] = await databaseAdminClient.updateDatabaseDdl({
    database: databaseAdminClient.databasePath(
      projectId,
      instanceId,
      databaseId,
    ),
    statements: request,
  });

  console.log(`Waiting for operation on ${databaseId} to complete...`);

  await operation.promise();

  console.log(
    `Added MarketingBudget column to Albums table in database ${databaseId}.`,
  );
}
pgAddColumn();

מריצים את הדוגמה באמצעות הארגומנט addColumn.

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

אמורה להופיע ההודעה:

Added the MarketingBudget column.

כתיבת נתונים בעמודה החדשה

הקוד הבא כותב נתונים בעמודה החדשה. היא מגדירה את MarketingBudget לערך 100000 בשורה עם מפתח השורה Albums(1, 1), ולערך 500000 בשורה עם מפתח השורה 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();
}

מריצים את הדוגמה באמצעות הארגומנט 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 בעסקה של קריאה וכתיבה.

משתמשים ב-method‏ runUpdate() כדי להריץ פקודת DML.

GoogleSQL

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

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 Spanner client library
const {Spanner} = require('@google-cloud/spanner');

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

function updateUsingDml() {
  // 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: 'UPDATE singers SET FirstName = $1 WHERE singerid = 1',
        params: {
          p1: 'Virginia',
        },
      });

      console.log(
        `Successfully updated ${rowCount} record in the Singers table.`,
      );

      await transaction.commit();
    } catch (err) {
      console.error('ERROR:', err);
    } finally {
      // Close the database when finished.
      await database.close();
    }
  });
}
updateUsingDml();

מריצים את הדוגמה באמצעות הארגומנט 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 בטווח מסוים. אפשר לקרוא את כל הערכים מהעמודה AlbumTitle באמצעות הצהרת SQL או קריאה, ואז למחוק את השורות שלא עומדות בקריטריונים. עם זאת, סריקה של כל הטבלה היא פעולה יקרה, במיוחד בטבלאות עם הרבה שורות. במקום זאת, כדי לאחזר שורות מהר יותר כשמחפשים לפי עמודות שהן לא מפתח ראשי, אפשר ליצור אינדקס משני בטבלה.

כדי להוסיף אינדקס משני לטבלה קיימת, צריך לעדכן את הסכימה. כמו עדכוני סכימה אחרים, Spanner תומך בהוספת אינדקס בזמן שמסד הנתונים ממשיך לשרת את התנועה. ‫Spanner ממלא באופן אוטומטי את החוסרים באינדקס באמצעות הנתונים הקיימים. מילויים חוזרים עשויים להימשך כמה דקות, אבל לא צריך להעביר את מסד הנתונים למצב אופליין או להימנע מכתיבה בטבלה שנוסף לה אינדקס במהלך התהליך הזה. למידע נוסף, קראו את המאמר הוספת אינדקס משני.

אחרי שמוסיפים אינדקס משני, Spanner משתמש בו באופן אוטומטי לשאילתות SQL שסביר להניח שיפעלו מהר יותר עם האינדקס. אם אתם משתמשים בממשק הקריאה, אתם צריכים לציין את האינדקס שבו אתם רוצים להשתמש.

הוספת אינדקס משני

אפשר להוסיף אינדקס בשורת הפקודה באמצעות ה-CLI של gcloud, או באופן פרוגרמטי באמצעות ספריית הלקוח של Spanner ל-Node.js.

בשורת הפקודה

משתמשים בפקודה הבאה של CREATE INDEX כדי להוסיף אינדקס למסד הנתונים:

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

אמורה להופיע ההודעה:

Schema updating...done.

שימוש בספריית הלקוח של Spanner ל-Node.js

משתמשים ב-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 משתמש באופן אוטומטי באינדקס מתאים. בממשק הקריאה, צריך לציין את האינדקס בבקשה.

כדי להשתמש באינדקס בממשק הקריאה, משתמשים ב-method‏ 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.

שימוש בספריית הלקוח של Spanner ל-Node.js

משתמשים ב-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.

עכשיו אפשר לבצע קריאה שמאחזרת את כל העמודות AlbumId,‏ AlbumTitle ו-MarketingBudget מהאינדקס 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();
}

מריצים את הדוגמה באמצעות הארגומנט 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

אחזור נתונים באמצעות עסקאות לקריאה בלבד

נניח שאתם רוצים לבצע יותר מקריאה אחת באותה חותמת זמן. עסקאות לקריאה בלבד צופות בתחילית עקבית של היסטוריית ה-commit של העסקה, כך שהאפליקציה תמיד מקבלת נתונים עקביים. משתמשים ב-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.

הסרת המשאבים

כדי להימנע מחיובים נוספים בחשבון לחיוב ב-Cloud על המשאבים שבהם השתמשתם במדריך הזה, אתם צריכים למחוק את מסד הנתונים ואת המכונה שיצרתם.

מחיקת מסד הנתונים

אם מוחקים מכונה, כל מסדי הנתונים שבתוכה נמחקים באופן אוטומטי. בשלב הזה מוסבר איך למחוק מסד נתונים בלי למחוק מכונה (עדיין תחויבו על המכונה).

בשורת הפקודה

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

באמצעות מסוף Google Cloud

  1. נכנסים לדף Spanner Instances במסוף Google Cloud .

    כניסה לדף Instances

  2. לוחצים על המופע.

  3. לוחצים על מסד הנתונים שרוצים למחוק.

  4. בדף Database details, לוחצים על Delete.

  5. מאשרים שרוצים למחוק את מסד הנתונים ולוחצים על מחיקה.

מחיקת המכונה

מחיקת מכונה גורמת להסרה אוטומטית של כל מסדי הנתונים שנוצרו בה.

בשורת הפקודה

gcloud spanner instances delete test-instance

באמצעות מסוף Google Cloud

  1. נכנסים לדף Spanner Instances במסוף Google Cloud .

    כניסה לדף Instances

  2. לוחצים על המכונה.

  3. לוחצים על Delete.

  4. מאשרים שרוצים למחוק את המכונה ולוחצים על Delete.

המאמרים הבאים