תחילת העבודה עם Spanner ב-C++‎

מטרות

במדריך הזה נסביר איך לבצע את השלבים הבאים באמצעות ספריית הלקוח Spanner ל-C++‎:

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

עלויות

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

לפני שמתחילים

צריך לבצע את השלבים שמפורטים במאמר הגדרה, שכוללים יצירה והגדרה של פרויקט ברירת מחדל Google Cloud , הפעלת החיוב, הפעלת Cloud Spanner API והגדרת OAuth 2.0 כדי לקבל אישורי אימות לשימוש ב-Cloud Spanner API.

בפרט, חשוב להריץ את הפקודה gcloud auth application-default login כדי להגדיר את סביבת הפיתוח המקומית עם פרטי אימות.

הכנת סביבת C++‎ מקומית

  1. משכפלים את מאגר האפליקציה לדוגמה ומעבירים אותו למכונה המקומית:

    git clone https://github.com/googleapis/google-cloud-cpp $HOME/google-cloud-cpp
    
  2. כדי להתקין את Bazel ל-Linux, צריך לפעול לפי ההוראות האלה.

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

    cd $HOME/google-cloud-cpp
    
  4. מריצים את הפקודה הבאה כדי ליצור את הדוגמאות:

    bazel build //google/cloud/spanner/samples:samples
    
  5. מגדירים אימות והרשאה עבור הפרויקט google-cloud-cpp.

    gcloud auth application-default login
    
  6. יוצרים משתנה סביבה בשם PROJECT_ID. מחליפים את [MY_PROJECT_ID] במזהה הפרויקט ב- Google Cloud . אפשר למצוא את המזהה הזה בדף Welcome של הפרויקט.

    export PROJECT_ID=[MY_PROJECT_ID]
    

יצירת מופע

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

במאמר יצירת מכונה מוסבר איך ליצור מכונת Spanner באמצעות אחת מהשיטות הבאות. אפשר לתת למופע שם test-instance כדי להשתמש בו עם נושאים אחרים במסמך הזה שמפנים למופע בשם test-instance.

  • ‫Google Cloud CLI
  • מסוף Google Cloud
  • ספריית לקוח (C++‎,‏ C#‎,‏ Go,‏ Java,‏ Node.js,‏ PHP,‏ Python או Ruby)

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

מאגר הדוגמאות מכיל דוגמה שמראה איך להשתמש ב-Spanner עם C++‎.

אפשר לעיין בקובץ google/cloud/spanner/samples/samples.cc כדי לראות איך יוצרים מסד נתונים ומשנים את הסכימה שלו. הנתונים משתמשים בסכימה לדוגמה שמוצגת בדף סכימה ומודל נתונים.

יצירת מסד נתונים

GoogleSQL

bazel run //google/cloud/spanner/samples:samples -- \
      create-database PROJECT_ID test-instance example-db

PostgreSQL

bazel run //google/cloud/spanner/samples:postgresql_samples -- \
      create-database PROJECT_ID test-instance example-db

bazel run //google/cloud/spanner/samples:postgresql_samples -- \
      interleaved-table PROJECT_ID test-instance example-db

הפרטים שמוצגים הם:

Created database [projects/${PROJECT_ID}/instances/test-instance/databases/example-db]
הקוד הבא יוצר מסד נתונים ושתי טבלאות במסד הנתונים.

GoogleSQL

void CreateDatabase(google::cloud::spanner_admin::DatabaseAdminClient client,
                    std::string const& project_id,
                    std::string const& instance_id,
                    std::string const& database_id) {
  google::cloud::spanner::Database database(project_id, instance_id,
                                            database_id);
  google::spanner::admin::database::v1::CreateDatabaseRequest request;
  request.set_parent(database.instance().FullName());
  request.set_create_statement("CREATE DATABASE `" + database.database_id() +
                               "`");
  request.add_extra_statements(R"""(
      CREATE TABLE Singers (
          SingerId   INT64 NOT NULL,
          FirstName  STRING(1024),
          LastName   STRING(1024),
          SingerInfo BYTES(MAX),
          FullName   STRING(2049)
              AS (ARRAY_TO_STRING([FirstName, LastName], " ")) STORED
      ) PRIMARY KEY (SingerId))""");
  request.add_extra_statements(R"""(
      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)""");
  auto db = client.CreateDatabase(request).get();
  if (!db) throw std::move(db).status();
  std::cout << "Database " << db->name() << " created.\n";
}

PostgreSQL

בניב PostgreSQL, צריך ליצור את מסד הנתונים לפני ששולחים בקשת DDL ליצירת טבלה.

בדוגמה הבאה נוצר מסד נתונים:

void CreateDatabase(google::cloud::spanner_admin::DatabaseAdminClient client,
                    google::cloud::spanner::Database const& database) {
  google::spanner::admin::database::v1::CreateDatabaseRequest request;
  request.set_parent(database.instance().FullName());
  request.set_create_statement("CREATE DATABASE \"" + database.database_id() +
                               "\"");
  request.set_database_dialect(
      google::spanner::admin::database::v1::DatabaseDialect::POSTGRESQL);
  auto db = client.CreateDatabase(request).get();
  if (!db) throw std::move(db).status();
  std::cout << "Database " << db->name() << " created.\n";
}

בדוגמה הבאה נוצרות שתי הטבלאות במסד הנתונים:

void InterleavedTable(google::cloud::spanner_admin::DatabaseAdminClient client,
                      google::cloud::spanner::Database const& database) {
  // The Spanner PostgreSQL dialect extends the PostgreSQL dialect with
  // certain Spanner specific features, such as interleaved tables. See
  // https://cloud.google.com/spanner/docs/postgresql/data-definition-language#create_table
  // for the full CREATE TABLE syntax.
  std::vector<std::string> statements = {
      R"""(
        CREATE TABLE Singers (
            SingerId        BIGINT NOT NULL,
            FirstName       CHARACTER VARYING(1024) NOT NULL,
            LastName        CHARACTER VARYING(1024) NOT NULL,
            PRIMARY KEY(SingerId)
        )
      )""",
      R"""(
        CREATE TABLE Albums (
            SingerId        BIGINT NOT NULL,
            AlbumId         BIGINT NOT NULL,
            AlbumTitle      CHARACTER VARYING NOT NULL,
            MarketingBudget BIGINT,
            PRIMARY KEY(SingerId, AlbumId)
        ) INTERLEAVE IN PARENT Singers ON DELETE CASCADE
      )""",
  };
  auto metadata =
      client.UpdateDatabaseDdl(database.FullName(), statements).get();
  google::cloud::spanner_testing::LogUpdateDatabaseDdl(  //! TODO(#4758)
      client, database, metadata.status());              //! TODO(#4758)
  if (!metadata) throw std::move(metadata).status();
  std::cout << "Tables created.\nNew DDL:\n" << metadata->DebugString();
}

השלב הבא הוא כתיבת נתונים למסד הנתונים.

יצירת לקוח מסד נתונים

לפני שתוכלו לבצע פעולות קריאה או כתיבה, אתם צריכים ליצור Client:

auto database = spanner::Database(project_id, instance_id, database_id);
auto connection = spanner::MakeConnection(database);
auto client = spanner::Client(connection);

Client מאפשר לכם לקרוא, לכתוב, לשלוח שאילתות ולבצע עסקאות במסד נתונים של Spanner. בדרך כלל יוצרים Client כשמפעילים את האפליקציה, ואז משתמשים שוב באותו Client כדי לקרוא, לכתוב ולהריץ עסקאות. כל לקוח משתמש במשאבים ב-Spanner. ה-destructor של Client מנקה אוטומטית את המשאבים של Client, כולל חיבורים לרשת.

מידע נוסף על Client זמין בGoogle Cloud Spanner C++ Reference.

כתיבת נתונים באמצעות DML

אפשר להוסיף נתונים באמצעות שפת טיפול בנתונים (DML) בעסקת קריאה-כתיבה.

משתמשים בפונקציה Client::ExecuteDml() כדי להריץ פקודת DML.

void DmlGettingStartedInsert(google::cloud::spanner::Client client) {
  using ::google::cloud::StatusOr;
  namespace spanner = ::google::cloud::spanner;

  auto commit_result = client.Commit(
      [&client](spanner::Transaction txn) -> StatusOr<spanner::Mutations> {
        auto insert = client.ExecuteDml(
            std::move(txn),
            spanner::SqlStatement(
                "INSERT INTO Singers (SingerId, FirstName, LastName) VALUES"
                " (12, 'Melissa', 'Garcia'),"
                " (13, 'Russell', 'Morales'),"
                " (14, 'Jacqueline', 'Long'),"
                " (15, 'Dylan', 'Shaw')"));
        if (!insert) return std::move(insert).status();
        return spanner::Mutations{};
      });
  if (!commit_result) throw std::move(commit_result).status();
  std::cout << "Insert was successful [spanner_dml_getting_started_insert]\n";
}

מריצים את הדוגמה באמצעות הארגומנט getting-started-insert.

bazel run //google/cloud/spanner/samples:samples -- \
    getting-started-insert PROJECT_ID test-instance example-db

הפרטים שמוצגים הם:

Insert was successful [spanner_dml_getting_started_insert]

כתיבת נתונים באמצעות מוטציות

אפשר גם להוסיף נתונים באמצעות מוטציות.

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

בדוגמה הבאה אפשר לראות איך לכתוב את הנתונים באמצעות מוטציות:

void InsertData(google::cloud::spanner::Client client) {
  namespace spanner = ::google::cloud::spanner;
  auto insert_singers = spanner::InsertMutationBuilder(
                            "Singers", {"SingerId", "FirstName", "LastName"})
                            .EmplaceRow(1, "Marc", "Richards")
                            .EmplaceRow(2, "Catalina", "Smith")
                            .EmplaceRow(3, "Alice", "Trentor")
                            .EmplaceRow(4, "Lea", "Martin")
                            .EmplaceRow(5, "David", "Lomond")
                            .Build();

  auto insert_albums = spanner::InsertMutationBuilder(
                           "Albums", {"SingerId", "AlbumId", "AlbumTitle"})
                           .EmplaceRow(1, 1, "Total Junk")
                           .EmplaceRow(1, 2, "Go, Go, Go")
                           .EmplaceRow(2, 1, "Green")
                           .EmplaceRow(2, 2, "Forever Hold Your Peace")
                           .EmplaceRow(2, 3, "Terrified")
                           .Build();

  auto commit_result =
      client.Commit(spanner::Mutations{insert_singers, insert_albums});
  if (!commit_result) throw std::move(commit_result).status();
  std::cout << "Insert was successful [spanner_insert_data]\n";
}

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

bazel run //google/cloud/spanner/samples:samples -- \
    insert-data PROJECT_ID test-instance example-db

הפרטים שמוצגים הם:

Insert was successful [spanner_insert_data]

הרצת שאילתות על נתונים באמצעות SQL

‫Spanner תומך בממשק SQL לקריאת נתונים, שאפשר לגשת אליו בשורת הפקודה באמצעות Google Cloud CLI או באופן פרוגרמטי באמצעות ספריית הלקוח של Spanner ל-C++.

בשורת הפקודה

מריצים את הצהרת ה-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

שימוש בספריית הלקוח של Spanner עבור C++‎

בנוסף להרצת הצהרת SQL בשורת הפקודה, אפשר להנפיק את אותה הצהרת SQL באופן פרוגרמטי באמצעות ספריית הלקוח Spanner ל-C++‎.

משתמשים בפונקציה Client::ExecuteQuery() כדי להריץ את שאילתת ה-SQL. כך מריצים את השאילתה וניגשים לנתונים:

void QueryData(google::cloud::spanner::Client client) {
  namespace spanner = ::google::cloud::spanner;

  spanner::SqlStatement select("SELECT SingerId, LastName FROM Singers");
  using RowType = std::tuple<std::int64_t, std::string>;
  auto rows = client.ExecuteQuery(std::move(select));
  for (auto& row : spanner::StreamOf<RowType>(rows)) {
    if (!row) throw std::move(row).status();
    std::cout << "SingerId: " << std::get<0>(*row) << "\t";
    std::cout << "LastName: " << std::get<1>(*row) << "\n";
  }

  std::cout << "Query completed for [spanner_query_data]\n";
}

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

bazel run //google/cloud/spanner/samples:samples -- \
    query-data PROJECT_ID test-instance example-db

אמורה להתקבל התוצאה הבאה:

SingerId: 1     LastName: Richards
SingerId: 2     LastName: Smith
SingerId: 3     LastName: Trentor
SingerId: 4     LastName: Martin
SingerId: 5     LastName: Lomond
SingerId: 12    LastName: Garcia
SingerId: 13    LastName: Morales
SingerId: 14    LastName: Long
SingerId: 15    LastName: Shaw

שאילתה באמצעות פרמטר SQL

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

הנה דוגמה לשימוש בפרמטר בקטע WHERE כדי לשלוח שאילתה לגבי רשומות שמכילות ערך ספציפי של LastName.

GoogleSQL

void QueryWithParameter(google::cloud::spanner::Client client) {
  namespace spanner = ::google::cloud::spanner;

  spanner::SqlStatement select(
      "SELECT SingerId, FirstName, LastName FROM Singers"
      " WHERE LastName = @last_name",
      {{"last_name", spanner::Value("Garcia")}});
  using RowType = std::tuple<std::int64_t, std::string, std::string>;
  auto rows = client.ExecuteQuery(std::move(select));
  for (auto& row : spanner::StreamOf<RowType>(rows)) {
    if (!row) throw std::move(row).status();
    std::cout << "SingerId: " << std::get<0>(*row) << "\t";
    std::cout << "FirstName: " << std::get<1>(*row) << "\t";
    std::cout << "LastName: " << std::get<2>(*row) << "\n";
  }

  std::cout << "Query completed for [spanner_query_with_parameter]\n";
}

PostgreSQL

void QueryWithParameter(google::cloud::spanner::Client client) {
  std::cout << "Listing all singers with a last name that starts with 'S'\n";
  auto sql = google::cloud::spanner::SqlStatement(
      "SELECT SingerId, FirstName, LastName FROM Singers"
      "  WHERE LastName LIKE $1",
      {{"p1", google::cloud::spanner::Value("S%")}});
  using RowType = std::tuple<std::int64_t, std::string, std::string>;
  auto rows = client.ExecuteQuery(std::move(sql));
  for (auto& row : google::cloud::spanner::StreamOf<RowType>(rows)) {
    if (!row) throw std::move(row).status();
    std::cout << "SingerId: " << std::get<0>(*row) << "\t";
    std::cout << "FirstName: " << std::get<1>(*row) << "\t";
    std::cout << "LastName: " << std::get<2>(*row) << "\n";
  }
  std::cout << "Query completed.\n";
}

מריצים את הדוגמה באמצעות הפקודה query-with-parameter.

bazel run //google/cloud/spanner/samples:samples -- \
    query-with-parameter PROJECT_ID test-instance example-db

אמורה להתקבל התוצאה הבאה:

SingerId: 12    FirstName: Melissa      LastName: Garcia

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

בנוסף לממשק ה-SQL של Spanner, ‏ Spanner תומך גם בממשק קריאה.

משתמשים בפונקציה Client::Read() כדי לקרוא שורות ממסד הנתונים. אפשר להשתמש באובייקט KeySet כדי להגדיר אוסף של מפתחות וטווחים של מפתחות לקריאה.

כך קוראים את הנתונים:

void ReadData(google::cloud::spanner::Client client) {
  namespace spanner = ::google::cloud::spanner;

  auto rows = client.Read("Albums", google::cloud::spanner::KeySet::All(),
                          {"SingerId", "AlbumId", "AlbumTitle"});
  using RowType = std::tuple<std::int64_t, std::int64_t, std::string>;
  for (auto& row : spanner::StreamOf<RowType>(rows)) {
    if (!row) throw std::move(row).status();
    std::cout << "SingerId: " << std::get<0>(*row) << "\t";
    std::cout << "AlbumId: " << std::get<1>(*row) << "\t";
    std::cout << "AlbumTitle: " << std::get<2>(*row) << "\n";
  }

  std::cout << "Read completed for [spanner_read_data]\n";
}

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

bazel run //google/cloud/spanner/samples:samples -- \
    read-data PROJECT_ID test-instance example-db

הפלט אמור להיראות כך:

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 עבור ++C.

בשורת הפקודה

כדי להוסיף את העמודה החדשה לטבלה, משתמשים בפקודה 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 עבור C++‎

כדי לשנות את הסכימה, משתמשים בפונקציה DatabaseAdminClient::UpdateDatabase().

GoogleSQL

void AddColumn(google::cloud::spanner_admin::DatabaseAdminClient client,
               std::string const& project_id, std::string const& instance_id,
               std::string const& database_id) {
  google::cloud::spanner::Database database(project_id, instance_id,
                                            database_id);
  auto metadata =
      client
          .UpdateDatabaseDdl(
              database.FullName(),
              {"ALTER TABLE Albums ADD COLUMN MarketingBudget INT64"})
          .get();
  if (!metadata) throw std::move(metadata).status();
  std::cout << "Added MarketingBudget column\n";
}

PostgreSQL

void AddColumn(google::cloud::spanner_admin::DatabaseAdminClient client,
               google::cloud::spanner::Database const& database) {
  std::vector<std::string> statements = {
      R"""(
        ALTER TABLE Albums
            ADD COLUMN MarketingBudget BIGINT
      )""",
  };
  auto metadata =
      client.UpdateDatabaseDdl(database.FullName(), statements).get();
  google::cloud::spanner_testing::LogUpdateDatabaseDdl(  //! TODO(#4758)
      client, database, metadata.status());              //! TODO(#4758)
  if (!metadata) throw std::move(metadata).status();
  std::cout << "Column added.\nNew DDL:\n" << metadata->DebugString();
}

מריצים את הדוגמה באמצעות הפקודה add-column.

bazel run //google/cloud/spanner/samples:samples -- \
    add-column PROJECT_ID test-instance example-db

הפרטים שמוצגים הם:

Added MarketingBudget column

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

הקוד הבא כותב נתונים בעמודה החדשה. הפונקציה מגדירה את MarketingBudget ל-100000 בשורה עם מפתח Albums(1, 1), ול-500000 בשורה עם מפתח Albums(2, 2).

void UpdateData(google::cloud::spanner::Client client) {
  namespace spanner = ::google::cloud::spanner;
  auto commit_result = client.Commit(spanner::Mutations{
      spanner::UpdateMutationBuilder("Albums",
                                     {"SingerId", "AlbumId", "MarketingBudget"})
          .EmplaceRow(1, 1, 100000)
          .EmplaceRow(2, 2, 500000)
          .Build()});
  if (!commit_result) throw std::move(commit_result).status();
  std::cout << "Update was successful [spanner_update_data]\n";
}

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

bazel run //google/cloud/spanner/samples:samples -- \
    update-data PROJECT_ID test-instance example-db

אפשר גם להריץ שאילתת SQL או קריאת נתונים כדי לאחזר את הערכים שזה עתה כתבתם.

הנה הקוד להרצת השאילתה:

void QueryNewColumn(google::cloud::spanner::Client client) {
  namespace spanner = ::google::cloud::spanner;

  spanner::SqlStatement select(
      "SELECT SingerId, AlbumId, MarketingBudget FROM Albums");
  using RowType =
      std::tuple<std::int64_t, std::int64_t, absl::optional<std::int64_t>>;

  auto rows = client.ExecuteQuery(std::move(select));
  for (auto& row : spanner::StreamOf<RowType>(rows)) {
    if (!row) throw std::move(row).status();
    std::cout << "SingerId: " << std::get<0>(*row) << "\t";
    std::cout << "AlbumId: " << std::get<1>(*row) << "\t";
    auto marketing_budget = std::get<2>(*row);
    if (marketing_budget) {
      std::cout << "MarketingBudget: " << *marketing_budget << "\n";
    } else {
      std::cout << "MarketingBudget: NULL\n";
    }
  }
  std::cout << "Read completed for [spanner_read_data_with_new_column]\n";
}

כדי להריץ את השאילתה הזו, מריצים את הדוגמה באמצעות הארגומנט query-new-column.

bazel run //google/cloud/spanner/samples:samples -- \
    query-new-column PROJECT_ID test-instance example-db

הפרטים שמוצגים הם:

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 בעסקת קריאה-כתיבה.

משתמשים בפונקציה Client::ExecuteDml() כדי להריץ פקודת DML.

GoogleSQL

void DmlGettingStartedUpdate(google::cloud::spanner::Client client) {
  using ::google::cloud::StatusOr;
  namespace spanner = ::google::cloud::spanner;

  // A helper to read the budget for the given album and singer.
  auto get_budget = [&](spanner::Transaction txn, std::int64_t album_id,
                        std::int64_t singer_id) -> StatusOr<std::int64_t> {
    auto key = spanner::KeySet().AddKey(spanner::MakeKey(album_id, singer_id));
    auto rows = client.Read(std::move(txn), "Albums", key, {"MarketingBudget"});
    using RowType = std::tuple<absl::optional<std::int64_t>>;
    auto row = spanner::GetSingularRow(spanner::StreamOf<RowType>(rows));
    if (!row) return std::move(row).status();
    auto const budget = std::get<0>(*row);
    return budget ? *budget : 0;
  };

  // A helper to update the budget for the given album and singer.
  auto update_budget = [&](spanner::Transaction txn, std::int64_t album_id,
                           std::int64_t singer_id, std::int64_t budget) {
    auto sql = spanner::SqlStatement(
        "UPDATE Albums SET MarketingBudget = @AlbumBudget"
        "  WHERE SingerId = @SingerId AND AlbumId = @AlbumId",
        {{"AlbumBudget", spanner::Value(budget)},
         {"AlbumId", spanner::Value(album_id)},
         {"SingerId", spanner::Value(singer_id)}});
    return client.ExecuteDml(std::move(txn), std::move(sql));
  };

  auto const transfer_amount = 20000;
  auto commit_result = client.Commit(
      [&](spanner::Transaction const& txn) -> StatusOr<spanner::Mutations> {
        auto budget1 = get_budget(txn, 1, 1);
        if (!budget1) return std::move(budget1).status();
        if (*budget1 < transfer_amount) {
          return google::cloud::Status(
              google::cloud::StatusCode::kUnknown,
              "cannot transfer " + std::to_string(transfer_amount) +
                  " from budget of " + std::to_string(*budget1));
        }
        auto budget2 = get_budget(txn, 2, 2);
        if (!budget2) return std::move(budget2).status();
        auto update = update_budget(txn, 1, 1, *budget1 - transfer_amount);
        if (!update) return std::move(update).status();
        update = update_budget(txn, 2, 2, *budget2 + transfer_amount);
        if (!update) return std::move(update).status();
        return spanner::Mutations{};
      });
  if (!commit_result) throw std::move(commit_result).status();
  std::cout << "Update was successful [spanner_dml_getting_started_update]\n";
}

PostgreSQL

void DmlGettingStartedUpdate(google::cloud::spanner::Client client) {
  // A helper to read the budget for the given album and singer.
  auto get_budget =
      [&](google::cloud::spanner::Transaction txn, std::int64_t album_id,
          std::int64_t singer_id) -> google::cloud::StatusOr<std::int64_t> {
    auto key = google::cloud::spanner::KeySet().AddKey(
        google::cloud::spanner::MakeKey(album_id, singer_id));
    auto rows = client.Read(std::move(txn), "Albums", key, {"MarketingBudget"});
    using RowType = std::tuple<absl::optional<std::int64_t>>;
    auto row = google::cloud::spanner::GetSingularRow(
        google::cloud::spanner::StreamOf<RowType>(rows));
    if (!row) return std::move(row).status();
    auto const budget = std::get<0>(*row);
    return budget ? *budget : 0;
  };

  // A helper to update the budget for the given album and singer.
  auto update_budget = [&](google::cloud::spanner::Transaction txn,
                           std::int64_t singer_id, std::int64_t album_id,
                           std::int64_t budget) {
    auto sql = google::cloud::spanner::SqlStatement(
        "UPDATE Albums SET MarketingBudget = $1"
        "  WHERE SingerId = $2 AND AlbumId = $3",
        {{"p1", google::cloud::spanner::Value(budget)},
         {"p2", google::cloud::spanner::Value(singer_id)},
         {"p3", google::cloud::spanner::Value(album_id)}});
    return client.ExecuteDml(std::move(txn), std::move(sql));
  };

  auto const transfer_amount = 20000;
  auto commit = client.Commit(
      [&](google::cloud::spanner::Transaction const& txn)
          -> google::cloud::StatusOr<google::cloud::spanner::Mutations> {
        auto budget1 = get_budget(txn, 1, 1);
        if (!budget1) return std::move(budget1).status();
        if (*budget1 < transfer_amount) {
          return google::cloud::Status(
              google::cloud::StatusCode::kUnknown,
              "cannot transfer " + std::to_string(transfer_amount) +
                  " from budget of " + std::to_string(*budget1));
        }
        auto budget2 = get_budget(txn, 2, 2);
        if (!budget2) return std::move(budget2).status();
        auto update = update_budget(txn, 1, 1, *budget1 - transfer_amount);
        if (!update) return std::move(update).status();
        update = update_budget(txn, 2, 2, *budget2 + transfer_amount);
        if (!update) return std::move(update).status();
        return google::cloud::spanner::Mutations{};
      });
  if (!commit) throw std::move(commit).status();
  std::cout << "Update was successful.\n";
}

מריצים את הדוגמה באמצעות הארגומנט getting-started-update.

bazel run //google/cloud/spanner/samples:samples -- \
    getting-started-update PROJECT_ID test-instance example-db

הפרטים שמוצגים הם:

Update was successful [spanner_dml_getting_started_update]

שימוש באינדקס משני

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

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

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

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

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

בשורת הפקודה

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

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

הפרטים שמוצגים הם:

Schema updating...done.

שימוש בספריית הלקוח של Spanner עבור C++‎

משתמשים בפונקציה DatabaseAdminClient::UpdateDatabase() כדי להוסיף אינדקס:

void AddIndex(google::cloud::spanner_admin::DatabaseAdminClient client,
              std::string const& project_id, std::string const& instance_id,
              std::string const& database_id) {
  google::cloud::spanner::Database database(project_id, instance_id,
                                            database_id);
  auto metadata =
      client
          .UpdateDatabaseDdl(
              database.FullName(),
              {"CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)"})
          .get();
  if (!metadata) throw std::move(metadata).status();
  std::cout << "`AlbumsByAlbumTitle` Index successfully added, new DDL:\n"
            << metadata->DebugString();
}

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

bazel run //google/cloud/spanner/samples:samples -- \
    add-index PROJECT_ID test-instance example-db

הוספת אינדקס יכולה להימשך כמה דקות. אחרי שהאינדקס נוסף, אמור להופיע פלט שדומה לזה:

`AlbumsByAlbumTitle` Index successfully added, new DDL:
database: "projects/PROJECT_ID/instances/test-instance/databases/example-db"
statements: "CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)"
commit_timestamps {
  seconds: 1581011550
  nanos: 531102000
}

קריאה באמצעות האינדקס

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

כדי להשתמש באינדקס בממשק הקריאה, צריך להשתמש בפונקציה Client::Read(), שקוראת אפס או יותר שורות ממסד נתונים באמצעות אינדקס.

הקוד הבא מאחזר את כל העמודות AlbumId ו-AlbumTitle מהאינדקס AlbumsByAlbumTitle.

void ReadDataWithIndex(google::cloud::spanner::Client client) {
  namespace spanner = ::google::cloud::spanner;

  auto rows =
      client.Read("Albums", google::cloud::spanner::KeySet::All(),
                  {"AlbumId", "AlbumTitle"},
                  google::cloud::Options{}.set<spanner::ReadIndexNameOption>(
                      "AlbumsByAlbumTitle"));
  using RowType = std::tuple<std::int64_t, std::string>;
  for (auto& row : spanner::StreamOf<RowType>(rows)) {
    if (!row) throw std::move(row).status();
    std::cout << "AlbumId: " << std::get<0>(*row) << "\t";
    std::cout << "AlbumTitle: " << std::get<1>(*row) << "\n";
  }
  std::cout << "Read completed for [spanner_read_data_with_index]\n";
}

מריצים את הדוגמה באמצעות הארגומנט read-data-with-index.

bazel run //google/cloud/spanner/samples:samples -- \
    read-data-with-index PROJECT_ID test-instance example-db

הפרטים שמוצגים הם:

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 עבור C++‎

משתמשים בפונקציה DatabaseAdminClient::UpdateDatabase() כדי להוסיף אינדקס עם פסקה STORING ל-:

void AddStoringIndex(google::cloud::spanner_admin::DatabaseAdminClient client,
                     std::string const& project_id,
                     std::string const& instance_id,
                     std::string const& database_id) {
  google::cloud::spanner::Database database(project_id, instance_id,
                                            database_id);
  auto metadata = client
                      .UpdateDatabaseDdl(database.FullName(), {R"""(
                        CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle)
                            STORING (MarketingBudget))"""})
                      .get();
  if (!metadata) throw std::move(metadata).status();
  std::cout << "`AlbumsByAlbumTitle2` Index successfully added, new DDL:\n"
            << metadata->DebugString();
}

מריצים את הדוגמה באמצעות הארגומנט add-storing-index.

bazel run //google/cloud/spanner/samples:samples -- \
    add-storing-index PROJECT_ID test-instance example-db

הפלט אמור להיראות כך:

`AlbumsByAlbumTitle2` Index successfully added, new DDL:
database: "projects/PROJECT_ID/instances/test-instance/databases/example-db"
statements: "CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) STORING (MarketingBudget)"
commit_timestamps {
  seconds: 1581012328
  nanos: 416682000
}

עכשיו אפשר להריץ קריאה שמביאה את כל העמודות AlbumId, AlbumTitle ו-MarketingBudget מהאינדקס AlbumsByAlbumTitle2:

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

void ReadDataWithStoringIndex(google::cloud::spanner::Client client) {
  namespace spanner = ::google::cloud::spanner;

  auto rows =
      client.Read("Albums", google::cloud::spanner::KeySet::All(),
                  {"AlbumId", "AlbumTitle", "MarketingBudget"},
                  google::cloud::Options{}.set<spanner::ReadIndexNameOption>(
                      "AlbumsByAlbumTitle2"));
  using RowType =
      std::tuple<std::int64_t, std::string, absl::optional<std::int64_t>>;
  for (auto& row : spanner::StreamOf<RowType>(rows)) {
    if (!row) throw std::move(row).status();
    std::cout << "AlbumId: " << std::get<0>(*row) << "\t";
    std::cout << "AlbumTitle: " << std::get<1>(*row) << "\t";
    auto marketing_budget = std::get<2>(*row);
    if (marketing_budget) {
      std::cout << "MarketingBudget: " << *marketing_budget << "\n";
    } else {
      std::cout << "MarketingBudget: NULL\n";
    }
  }
  std::cout << "Read completed for [spanner_read_data_with_storing_index]\n";
}

מריצים את הדוגמה באמצעות הארגומנט read-data-with-storing-index.

bazel run //google/cloud/spanner/samples:samples -- \
    read-data-with-storing-index PROJECT_ID test-instance example-db

הפלט אמור להיראות כך:

AlbumId: 2  AlbumTitle: Forever Hold Your Peace MarketingBudget: 520000
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: 80000

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

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

בדוגמה הבאה מוצג איך להריץ שאילתה ולבצע קריאה באותה טרנזקציה לקריאה בלבד:

void ReadOnlyTransaction(google::cloud::spanner::Client client) {
  namespace spanner = ::google::cloud::spanner;
  auto read_only = spanner::MakeReadOnlyTransaction();

  spanner::SqlStatement select(
      "SELECT SingerId, AlbumId, AlbumTitle FROM Albums");
  using RowType = std::tuple<std::int64_t, std::int64_t, std::string>;

  // Read#1.
  auto rows1 = client.ExecuteQuery(read_only, select);
  std::cout << "Read 1 results\n";
  for (auto& row : spanner::StreamOf<RowType>(rows1)) {
    if (!row) throw std::move(row).status();
    std::cout << "SingerId: " << std::get<0>(*row)
              << " AlbumId: " << std::get<1>(*row)
              << " AlbumTitle: " << std::get<2>(*row) << "\n";
  }
  // Read#2. Even if changes occur in-between the reads the transaction ensures
  // that Read #1 and Read #2 return the same data.
  auto rows2 = client.ExecuteQuery(read_only, select);
  std::cout << "Read 2 results\n";
  for (auto& row : spanner::StreamOf<RowType>(rows2)) {
    if (!row) throw std::move(row).status();
    std::cout << "SingerId: " << std::get<0>(*row)
              << " AlbumId: " << std::get<1>(*row)
              << " AlbumTitle: " << std::get<2>(*row) << "\n";
  }
}

מריצים את הדוגמה באמצעות הארגומנט read-only-transaction.

bazel run //google/cloud/spanner/samples:samples -- \
    read-only-transaction PROJECT_ID test-instance example-db

הפלט אמור להיראות כך:

Read 1 results
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
Read 2 results
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

הסרת המשאבים

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

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

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

בשורת הפקודה

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

שימוש במסוף Google Cloud

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

    כניסה לדף Instances

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

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

  4. בדף פרטי מסד הנתונים, לוחצים על מחיקה.

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

מחיקת המכונה

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

בשורת הפקודה

gcloud spanner instances delete test-instance

שימוש במסוף Google Cloud

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

    כניסה לדף Instances

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

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

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

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