## 模块6：并发控制 - 悲观模式 vs 乐观模式

在所有支持并发访问的数据库中，都存在冲突的风险，多个事务可能同时尝试更新相同的数据。虽然设计良好的数据模型可以最小化此类冲突的可能性，但它们仍然存在可能性并且必须得到有效管理。数据库提供处理并发数据访问的机制，两种主要方法是悲观和乐观并发控制。TiDB支持这两种方法。

本模块展示TiDB中悲观锁和乐观锁的行为。

### 步骤1. 阅读悲观模式演示代码

+ 在左侧VS Code的**EXPLORER**部分，点击`DemoJdbcPessimisticLock.java`文件。

+ 在以下代码片段中，启动了两个线程。每个线程将创建到TiDB的连接。

  ```output
  ...
  for (int i = 0; i < 2; i++) {
      connections.add(DriverManager.getConnection(connection_string));
  }
  ...
  ```

+ 在main方法中，将启动两个线程并尝试更新同一条记录。线程1和线程2都禁用了自动提交。

  ```java
  new Thread(new DemoJdbcPessimisticLock.RowUpdater(0, id, 1, 6000)).start();
  new Thread(new DemoJdbcPessimisticLock.RowUpdater(1, id, 1000, 2000)).start();
  ```

### 步骤2. 在悲观模式下运行演示代码

+ 在悲观模式下运行演示代码并观察输出。注意线程2在线程1提交之前无法提交。显示了一个可能的输出。

  ```bash
  rm -f DemoJdbcTxPessimisticLock.class
  javac -cp mysql-connector-j-9.1.0.jar DemoJdbcPessimisticLock.java
  sleep 1
  java -cp :mysql-connector-j-9.1.0.jar DemoJdbcPessimisticLock
  ```

  ```output
  Connection established.
  Connection B session started
  Connection A session started
  Connection A session: BEGIN PESSIMISTIC
  Connection A session: UPDATE test_tx_pessimistic SET name = 'Connection A' WHERE id = 5188146770730811393
  Connection B session: BEGIN PESSIMISTIC
  Connection B session: UPDATE test_tx_pessimistic SET name = 'Connection B' WHERE id = 5188146770730811393
  Connection A session: Commit
  Connection A session: Checking result

  /* Executing query: select id, name from test_tx_pessimistic; */
          Row#, id, name
          1) 5188146770730811393, Connection A

  Connection B session: Commit
  Connection B session: Checking result

  /* Executing query: select id, name from test_tx_pessimistic; */
          Row#, id, name
          1) 5188146770730811393, Connection B
  ```

### 步骤3. 阅读乐观模式演示代码

+ 在左侧VS Code的**EXPLORER**部分，点击`DemoJdbcOptimisticLock.java`文件。

+ 与悲观模式中的代码类似，启动两个线程并尝试同时更新同一行。

  ```java
  new Thread(new DemoJdbcOptimisticLock.RowUpdater(0, id, retryCommit, 1, 6000, 1000)).start();
  new Thread(new DemoJdbcOptimisticLock.RowUpdater(1, id, retryCommit, 1000, 2000, 9000)).start();
  ```

+ 在冲突的情况下，实现了简单的重试。

  ```java
  if (this.retryCommit) {
    try {
        Thread.sleep(this.waitBefore2ndCommit);
    } catch (InterruptedException ie) {
        ie.printStackTrace();
    }
    try {
        System.out.println(connectionTags[this.connectionNo] + " session: " + "Commit");
        Statement s = c.createStatement();
        s.executeUpdate("UPDATE test_tx_optimistic SET name = '" + connectionTags[this.connectionNo]
                + "' WHERE id = " + rowid);
        c.commit();
    } catch (SQLException e1) {
        e1.printStackTrace();
    }
  }
  ```

### 步骤4. 在乐观模式下运行演示

+ 您可以使用以下命令运行乐观锁的演示代码。我们首先使用`no-retry`运行代码，然后使用`retry`。显示了可能的输出。

  ```bash
  rm -f DemoJdbcOptimisticLock.class
  javac -cp mysql-connector-j-9.1.0.jar DemoJdbcOptimisticLock.java
  sleep 1
  java -cp :mysql-connector-j-9.1.0.jar DemoJdbcOptimisticLock no-retry
  ```

  ```output
  Connection established.
  Connection A session started
  Connection B session started
  Connection A session: BEGIN OPTIMISTIC
  Connection A session: UPDATE test_tx_optimistic SET name = 'Connection A' WHERE id = 4899916394579099649
  Connection B session: BEGIN OPTIMISTIC
  Connection B session: UPDATE test_tx_optimistic SET name = 'Connection B' WHERE id = 4899916394579099649
  Connection B session: Commit
  Connection B session: Checking result

  /* Executing query: select id, name from test_tx_optimistic; */
          Row#, id, name
          1) 4899916394579099649, Connection B

  Connection A session: Commit
  Connection A ErrorCode: 9007
  Connection A SQLState: HY000
  Connection A Error: java.sql.SQLException: Write conflict, txnStartTS=458625318024118280, conflictStartTS=458625318286262286, conflictCommitTS=458625318889193486, key=????, reason=Optimistic [try again later]
  < Session in Connection A RAISED THE EXCEPTION !!! >
  Connection A session: Checking result

  /* Executing query: select id, name from test_tx_optimistic; */
          Row#, id, name
          1) 4899916394579099649, Connection B
  ```

  ```bash
  rm -f DemoJdbcOptimisticLock.class
  javac -cp mysql-connector-j-9.1.0.jar DemoJdbcOptimisticLock.java
  sleep 1
  java -cp :mysql-connector-j-9.1.0.jar DemoJdbcOptimisticLock retry
  ```

  ```output
  Connection established.
  Connection A session started
  Connection B session started
  Connection A session: BEGIN OPTIMISTIC
  Connection A session: UPDATE test_tx_optimistic SET name = 'Connection A' WHERE id = 2017612633061982209
  Connection B session: BEGIN OPTIMISTIC
  Connection B session: UPDATE test_tx_optimistic SET name = 'Connection B' WHERE id = 2017612633061982209
  Connection B session: Commit
  Connection B session: Checking result

  /* Executing query: select id, name from test_tx_optimistic; */
          Row#, id, name
          1) 2017612633061982209, Connection B

  Connection A session: Commit
  Connection A ErrorCode: 9007
  Connection A SQLState: HY000
  Connection A Error: java.sql.SQLException: Write conflict, txnStartTS=458625338366492677, conflictStartTS=458625338628898820, conflictCommitTS=458625339231567877, key=????, reason=Optimistic [try again later]
  < Session in Connection A RAISED THE EXCEPTION !!! >
  Connection A session: Commit
  Connection A session: Checking result

  /* Executing query: select id, name from test_tx_optimistic; */
          Row#, id, name
          1) 2017612633061982209, Connection A
  ```

### 步骤 5. 前往 TiDB Cloud 控制台，开启你自己的旅程

+ 动手实验到此结束。你现在可以[点击这里](https://tidbcloud.com/clusters)开始你的 TiDB Cloud 之旅。如果你在本实验中使用了自己的免费集群，你可以在同一页面上找到它，集群名称为 ${LAB:SERVERLESS_CLUSTER_NAME}。

+ TiDB Cloud 提供了一个基于 Web 的 SQL 编辑器，你可以用它来探索 TiDB Cloud。无需在你的笔记本电脑上进行任何额外设置。

![sql_editor.png 85%](https://lab-static.pingcap.com/images/2026/3/1/da0e9e005e2d10932b0c62b497d3c3282fc58567.png)

+ 如果你不想自己编写 SQL 查询，没有问题。`Chat2Query` 是一个可以让你用自然语言编写查询的工具。
  + 首先，将以下命令复制到 SQL 编辑器中，选择你要使用的数据库，然后点击右上角的 `Run`。

    ```sql
    USE test;
    ```

    ![demo701_chat2query.png 85%](https://lab-static.pingcap.com/images/2026/3/2/cb2f9fbf97c188815f9c14a5cf329b71a966842b.png)

  + 然后，在 Windows 笔记本电脑上按 `ctrl` + `i`，在 Mac 上按 `command` + `i`，即可试用 TiDB `Chat2Query`。你可以用自然语言编写查询，AI 会为你自动生成查询语句。祝你使用愉快！

{% if SERVERLESS_CLUSTER_ENVIRONMENT == 'user_hosted' %}

+ 以下是两个示例：

  + 在示例 1 中，向 `Chat2Query` 输入 `show me the number of records in the table online_ddl_test`。查询生成后，检查确认无误，点击 `Accept`。最后，点击右上角的 `Run`。

  ![demo701_chat2query_eg1.png 85%](https://lab-static.pingcap.com/images/2026/3/2/d96c3389ad5f2aec461fc9e66995008a14fd7478.png)

  + 在示例 2 中，向 `Chat2Query` 输入 `add a column 'c' for me in table t1`。查询生成后，检查确认无误，点击 `Accept`。最后，点击右上角的 `Run`。

  ![demo701_chat2query_eg2.png 85%](https://lab-static.pingcap.com/images/2026/3/2/779fa0129a442787e40ef0e665ad0d6120d20984.png)

{% endif %}
