📣

TiDB Cloud Serverless が
Starter
に変わりました!このページは自動翻訳されたものです。
原文はこちらからご覧ください。

データの更新

このドキュメントでは、さまざまなプログラミング言語で次の SQL ステートメントを使用して TiDB のデータを更新する方法について説明します。

  • アップデート : 指定されたテーブル内のデータを変更するために使用されます。
  • 重複キー更新時の挿入 : 主キーまたは一意キーの競合がある場合に、データを挿入し、そのデータを更新するために使用されます。一意キー(主キーを含む)が複数ある場合は、このステートメントの使用は推奨されません。これは、このステートメントが一意キー(主キーを含む)の競合を検出すると、すぐにデータを更新するためです。複数の行の競合がある場合は、1行のみが更新されます。

始める前に

このドキュメントを読む前に、次のものを準備する必要があります。

UPDATEを使用する

テーブル内の既存の行を更新するには、 UPDATEステートメントWHERE句を使用して更新する列をフィルターする必要があります。

注記:

多数の行(例えば1万行以上)を更新する必要がある場合は、一度にすべての行を更新するのではなく、すべての行が更新されるまで、部分的な更新を繰り返すことをお勧めします。この操作をループさせるスクリプトやプログラムを作成することもできます。詳細は一括更新ご覧ください。

UPDATE SQL構文

SQL では、 UPDATE文は通常次の形式になります。

UPDATE {table} SET {update_column} = {update_value} WHERE {filter_column} = {filter_value}
パラメータ名説明
{table}テーブル名
{update_column}更新するカラム名
{update_value}更新するカラムの値
{filter_column}フィルターに一致するカラム名
{filter_value}フィルターに一致するカラムの値

詳細についてはUPDATE構文参照してください。

ベストプラクティスUPDATE

次に、データを更新するためのベスト プラクティスをいくつか示します。

  • UPDATEステートメントでは必ずWHERE句を指定してください。5 UPDATEステートメントにWHERE句がない場合、TiDBはテーブル内のすべての行を更新します。
  • 多数の行(例えば1万行以上)を更新する必要がある場合は、 一括更新使用してください。TiDBはデフォルトで1トランザクションのサイズを100MBに制限しているため、一度に大量のデータ更新を行うと、ロックが長時間保持される( 悲観的取引 )か、競合が発生する( 楽観的取引 )可能性があります。

UPDATE

ある著者がHelen Harukiに改名したとします。3 著者テーブルを変更する必要があります。彼女の固有id1だとすると、フィルターはid = 1になります。

    UPDATE `authors` SET `name` = "Helen Haruki" WHERE `id` = 1;
    // ds is an entity of com.mysql.cj.jdbc.MysqlDataSource try (Connection connection = ds.getConnection()) { PreparedStatement pstmt = connection.prepareStatement("UPDATE `authors` SET `name` = ? WHERE `id` = ?"); pstmt.setString(1, "Helen Haruki"); pstmt.setInt(2, 1); pstmt.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); }

    INSERT ON DUPLICATE KEY UPDATEを使用する

    テーブルに新しいデータを挿入する必要があるが、一意キー(主キーも一意キー)の競合が発生した場合、最初に競合が発生したレコードが更新されます。1 INSERT ... ON DUPLICATE KEY UPDATE ...ステートメントで挿入または更新を実行できます。

    INSERT ON DUPLICATE KEY UPDATE SQL構文

    SQL では、 INSERT ... ON DUPLICATE KEY UPDATE ...文は通常次の形式になります。

    INSERT INTO {table} ({columns}) VALUES ({values}) ON DUPLICATE KEY UPDATE {update_column} = {update_value};
    パラメータ名説明
    {table}テーブル名
    {columns}挿入するカラム名
    {values}挿入するカラムの値
    {update_column}更新するカラム名
    {update_value}更新するカラムの値

    INSERT ON DUPLICATE KEY UPDATEベストプラクティス

    • INSERT ON DUPLICATE KEY UPDATE 、一意のキーが 1 つしかないテーブルにのみ使用してください。このステートメントは、一意のキー(主キーを含む)の競合が検出された場合、データを更新します。競合行が複数ある場合、更新されるのは 1 行のみです。したがって、競合行が 1 行だけであることを保証できない限り、複数の一意のキーを持つテーブルでINSERT ON DUPLICATE KEY UPDATEステートメントを使用することは推奨されません。
    • データを作成または更新するときにこのステートメントを使用します。

    INSERT ON DUPLICATE KEY UPDATE

    例えば、 評価テーブルを更新して、ユーザーによる書籍の評価を含める必要があります。ユーザーがまだ書籍を評価していない場合は、新しい評価が作成されます。ユーザーがすでに評価している場合は、以前の評価が更新されます。

    次の例では、主キーはbook_iduser_idの結合主キーです。ユーザーuser_id = 1書籍book_id = 10005の評価を付けています。

      INSERT INTO `ratings` (`book_id`, `user_id`, `score`, `rated_at`) VALUES (1000, 1, 5, NOW()) ON DUPLICATE KEY UPDATE `score` = 5, `rated_at` = NOW();
      // ds is an entity of com.mysql.cj.jdbc.MysqlDataSource try (Connection connection = ds.getConnection()) { PreparedStatement p = connection.prepareStatement("INSERT INTO `ratings` (`book_id`, `user_id`, `score`, `rated_at`) VALUES (?, ?, ?, NOW()) ON DUPLICATE KEY UPDATE `score` = ?, `rated_at` = NOW()"); p.setInt(1, 1000); p.setInt(2, 1); p.setInt(3, 5); p.setInt(4, 5); p.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); }

      一括更新

      テーブル内の複数のデータ行を更新する必要がある場合は、 INSERT ON DUPLICATE KEY UPDATEを使用するWHERE句を使用して、更新する必要があるデータをフィルター処理できます。

      ただし、多数の行(たとえば1万行以上)を更新する必要がある場合は、データを繰り返し更新することをお勧めします。つまり、更新が完了するまで、各反復でデータの一部のみを更新します。これは、TiDBが1トランザクションのサイズをデフォルトで100 MBに制限しているためです。一度に多くのデータを更新すると、ロックが長時間保持される( 悲観的取引 )、または競合が発生する( 楽観的取引 )ことになります。プログラムまたはスクリプトでループを使用して、操作を完了することができます。

      このセクションでは、反復更新を処理するスクリプトの記述例を示します。この例では、 SELECTUPDATE組み合わせて一括更新を実行する方法を示します。

      一括更新ループを書く

      まず、アプリケーションまたはスクリプトのループ内にSELECTクエリを記述します。このクエリの戻り値は、更新が必要な行の主キーとして使用できます。このSELECTクエリを定義する際は、 WHERE番目の句を使用して更新が必要な行をフィルタリングする必要があることに注意してください。

      過去1年間、あなたのウェブサイトbookshopでユーザーから多くの書籍評価が寄せられたとします。しかし、当初の5段階評価の設計では、書籍評価の差別化が不十分でした。ほとんどの書籍は3評価です。そこで、評価を差別化するために、5段階評価から10段階評価に変更することにしました。

      先ほどの5段階評価のratings番目のテーブルのデータを2倍し、評価テーブルに新しい列を追加して、行が更新されたかどうかを示します。この列を使用することで、 SELECTに更新された行を除外できます。これにより、スクリプトがクラッシュして行を複数回更新し、不合理なデータが生成されることを防ぐことができます。

      たとえば、10 点スケールかどうかの識別子として、データ型ブールを持つten_pointという名前の列を作成します。

      ALTER TABLE `bookshop`.`ratings` ADD COLUMN `ten_point` BOOL NOT NULL DEFAULT FALSE;

      注記:

      この一括更新アプリケーションは、 DDL文を使用してデータテーブルのスキーマを変更します。TiDBのすべてのDDL変更操作はオンラインで実行されます。詳細については、 列を追加参照してください。

        Golangでは、一括更新アプリケーションは次のようになります。

        package main import ( "database/sql" "fmt" _ "github.com/go-sql-driver/mysql" "strings" "time" ) func main() { db, err := sql.Open("mysql", "root:@tcp(127.0.0.1:4000)/bookshop") if err != nil { panic(err) } defer db.Close() bookID, userID := updateBatch(db, true, 0, 0) fmt.Println("first time batch update success") for { time.Sleep(time.Second) bookID, userID = updateBatch(db, false, bookID, userID) fmt.Printf("batch update success, [bookID] %d, [userID] %d\n", bookID, userID) } } // updateBatch select at most 1000 lines data to update score func updateBatch(db *sql.DB, firstTime bool, lastBookID, lastUserID int64) (bookID, userID int64) { // select at most 1000 primary keys in five-point scale data var err error var rows *sql.Rows if firstTime { rows, err = db.Query("SELECT `book_id`, `user_id` FROM `bookshop`.`ratings` " + "WHERE `ten_point` != true ORDER BY `book_id`, `user_id` LIMIT 1000") } else { rows, err = db.Query("SELECT `book_id`, `user_id` FROM `bookshop`.`ratings` "+ "WHERE `ten_point` != true AND `book_id` > ? AND `user_id` > ? "+ "ORDER BY `book_id`, `user_id` LIMIT 1000", lastBookID, lastUserID) } if err != nil || rows == nil { panic(fmt.Errorf("error occurred or rows nil: %+v", err)) } // joint all id with a list var idList []interface{} for rows.Next() { var tempBookID, tempUserID int64 if err := rows.Scan(&tempBookID, &tempUserID); err != nil { panic(err) } idList = append(idList, tempBookID, tempUserID) bookID, userID = tempBookID, tempUserID } bulkUpdateSql := fmt.Sprintf("UPDATE `bookshop`.`ratings` SET `ten_point` = true, "+ "`score` = `score` * 2 WHERE (`book_id`, `user_id`) IN (%s)", placeHolder(len(idList))) db.Exec(bulkUpdateSql, idList...) return bookID, userID } // placeHolder format SQL place holder func placeHolder(n int) string { holderList := make([]string, n/2, n/2) for i := range holderList { holderList[i] = "(?,?)" } return strings.Join(holderList, ",") }

        各反復で、 SELECT主キーの順にクエリを実行します。10 段階評価 ( ten_pointfalse ) に更新されていない最大1000行の主キー値を選択します。各SELECT文では、重複を防ぐため、前のSELECT結果の最大値よりも大きい主キーを選択します。次に、一括更新を使用し、そのscore列を2で乗算して、 ten_pointtrueに設定します。 ten_point更新する目的は、クラッシュ後の再起動時に更新アプリケーションが同じ行を繰り返し更新してデータ破損を引き起こすのを防ぐことです。 time.Sleep(time.Second)各ループで、更新アプリケーションを 1 秒間一時停止して、更新アプリケーションがハードウェア リソースを過剰に消費するのを防ぎます。

        Java (JDBC) では、一括更新アプリケーションは次のようになります。

        コード:

        package com.pingcap.bulkUpdate; import com.mysql.cj.jdbc.MysqlDataSource; import java.sql.*; import java.util.LinkedList; import java.util.List; import java.util.concurrent.TimeUnit; public class BatchUpdateExample { static class UpdateID { private Long bookID; private Long userID; public UpdateID(Long bookID, Long userID) { this.bookID = bookID; this.userID = userID; } public Long getBookID() { return bookID; } public void setBookID(Long bookID) { this.bookID = bookID; } public Long getUserID() { return userID; } public void setUserID(Long userID) { this.userID = userID; } @Override public String toString() { return "[bookID] " + bookID + ", [userID] " + userID ; } } public static void main(String[] args) throws InterruptedException { // Configure the example database connection. // Create a mysql data source instance. MysqlDataSource mysqlDataSource = new MysqlDataSource(); // Set server name, port, database name, username and password. mysqlDataSource.setServerName("localhost"); mysqlDataSource.setPortNumber(4000); mysqlDataSource.setDatabaseName("bookshop"); mysqlDataSource.setUser("root"); mysqlDataSource.setPassword(""); UpdateID lastID = batchUpdate(mysqlDataSource, null); System.out.println("first time batch update success"); while (true) { TimeUnit.SECONDS.sleep(1); lastID = batchUpdate(mysqlDataSource, lastID); System.out.println("batch update success, [lastID] " + lastID); } } public static UpdateID batchUpdate (MysqlDataSource ds, UpdateID lastID) { try (Connection connection = ds.getConnection()) { UpdateID updateID = null; PreparedStatement selectPs; if (lastID == null) { selectPs = connection.prepareStatement( "SELECT `book_id`, `user_id` FROM `bookshop`.`ratings` " + "WHERE `ten_point` != true ORDER BY `book_id`, `user_id` LIMIT 1000"); } else { selectPs = connection.prepareStatement( "SELECT `book_id`, `user_id` FROM `bookshop`.`ratings` "+ "WHERE `ten_point` != true AND `book_id` > ? AND `user_id` > ? "+ "ORDER BY `book_id`, `user_id` LIMIT 1000"); selectPs.setLong(1, lastID.getBookID()); selectPs.setLong(2, lastID.getUserID()); } List<Long> idList = new LinkedList<>(); ResultSet res = selectPs.executeQuery(); while (res.next()) { updateID = new UpdateID( res.getLong("book_id"), res.getLong("user_id") ); idList.add(updateID.getBookID()); idList.add(updateID.getUserID()); } if (idList.isEmpty()) { System.out.println("no data should update"); return null; } String updateSQL = "UPDATE `bookshop`.`ratings` SET `ten_point` = true, "+ "`score` = `score` * 2 WHERE (`book_id`, `user_id`) IN (" + placeHolder(idList.size() / 2) + ")"; PreparedStatement updatePs = connection.prepareStatement(updateSQL); for (int i = 0; i < idList.size(); i++) { updatePs.setLong(i + 1, idList.get(i)); } int count = updatePs.executeUpdate(); System.out.println("update " + count + " data"); return updateID; } catch (SQLException e) { e.printStackTrace(); } return null; } public static String placeHolder(int n) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < n ; i++) { sb.append(i == 0 ? "(?,?)" : ",(?,?)"); } return sb.toString(); } }
        • hibernate.cfg.xml構成:
        <?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <!-- Database connection settings --> <property name="hibernate.connection.driver_class">com.mysql.cj.jdbc.Driver</property> <property name="hibernate.dialect">org.hibernate.dialect.TiDBDialect</property> <property name="hibernate.connection.url">jdbc:mysql://localhost:4000/movie</property> <property name="hibernate.connection.username">root</property> <property name="hibernate.connection.password"></property> <property name="hibernate.connection.autocommit">false</property> <property name="hibernate.jdbc.batch_size">20</property> <!-- Optional: Show SQL output for debugging --> <property name="hibernate.show_sql">true</property> <property name="hibernate.format_sql">true</property> </session-factory> </hibernate-configuration>

        各反復で、 SELECT主キーの順にクエリを実行します。10 段階評価 ( ten_pointfalse ) に更新されていない最大1000行の主キー値を選択します。各SELECT文では、重複を防ぐため、前のSELECT結果の最大値よりも大きい主キーを選択します。次に、一括更新を使用し、そのscore列を2で乗算して、 ten_pointtrueに設定します。 ten_point更新する目的は、クラッシュ後の再起動時に更新アプリケーションが同じ行を繰り返し更新してデータ破損を引き起こすのを防ぐことです。 TimeUnit.SECONDS.sleep(1);各ループで、更新アプリケーションを 1 秒間一時停止して、更新アプリケーションがハードウェア リソースを過剰に消費するのを防ぎます。

        ヘルプが必要ですか?

        不和またはスラック 、あるいはサポートチケットを送信するについてコミュニティに質問してください。

        このページは役に立ちましたか?