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

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

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

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

+ 在 VS Code 左侧的 **EXPLORER** 中，在 `mysql_connector_python_demo` 文件夹下，点击 `lock_pessimistic.py` 文件。让我们来简单读一下关键代码。

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

  ```output
  ...
  t1 = threading.Thread(target=thread1_task)
  t2 = threading.Thread(target=thread2_task)
  t1.start()
  t2.start()
  ...
  ```

+ 在以下代码中，使用 `threading.Event` 来协调线程 1 和线程 2 的动作，以确保冲突一定会发生。线程 1 和 2 都禁用了自动提交。

  ```output
  def thread1_task():
    conn1 = get_connection(autocommit=False)
    cur1 = conn1.cursor(dictionary=True)
    cur1.execute("BEGIN PESSIMISTIC;")
    print("[Thread1] BEGIN PESSIMISTIC", flush=True)

    # notify thread2 to begin transaction
    thread1_begin.set()

    # update and hold lock
    cur1.execute("UPDATE users SET name = 'conn1' WHERE id = 1;")
    print("[Thread1] UPDATED row → name='conn1'", flush=True)

    # notify thread2 to begin transaction
    thread1_updated.set()

    # wait thread2 to attempt update (blocked)
    thread2_ready.wait()

    cur1.execute("COMMIT;")
    print("[Thread1] COMMITTED", flush=True)
    ...
  ```

  ```output
  def thread2_task():
    conn2 = get_connection(autocommit=False)
    cur2 = conn2.cursor(dictionary=True)

    # wait thread1 to complete BEGIN
    thread1_begin.wait()

    cur2.execute("BEGIN PESSIMISTIC;")
    print("[Thread2] BEGIN PESSIMISTIC", flush=True)

    # wait thread1 to update and hold lock
    thread1_updated.wait()

    # notify thread1 that I will attempt UPDATE
    thread2_ready.set()
    print("[Thread2] attempting UPDATE (will block)...", flush=True)

    # this line will be blocked until thread1 COMMIT release row lock
    cur2.execute("UPDATE users SET name = 'conn2' WHERE id = 1;")
    print("[Thread2] UPDATED row → name='conn2'", flush=True)

    cur2.execute("COMMIT;")
    print("[Thread2] COMMITTED", flush=True)
    ...
  ```

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

+ 在 VS Code 左侧的 **EXPLORER** 中，在 `mysql_connector_python_demo` 文件夹下，点击 `lock_pessimistic.py` 文件。请阅读源代码。

+ 在悲观模式下运行演示代码并观察输出。注意线程二在线程 1 提交之前无法提交。

  ```bash
  poetry run python mysql_connector_python_demo/lock_pessimistic.py
  ```

  ```output
  Create database lab_demo_405_3450001
  [Thread1] BEGIN PESSIMISTIC
  [Thread2] BEGIN PESSIMISTIC
  [Thread1] UPDATED row → name='conn1'
  [Thread2] attempting UPDATE (will block)...
  [Thread1] COMMITTED
  [Thread2] UPDATED row → name='conn2'
  [Thread2] COMMITTED
  Update Result: name is changed to conn2
  ```

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

+ 在 VS Code 左侧的 **EXPLORER** 中，在 `mysql_connector_python_demo` 文件夹下，点击 `lock_optimistic.py` 文件。请阅读源代码。

+ 乐观锁的演示代码与悲观锁的类似，不同之处在于两个线程都实现了 `try` 和 `except` 逻辑。此外，乐观锁的演示代码中没有使用 `threading.Event`。

+ 我们以线程 1 为例。

  ```output
  def thread1_task():
    ...
    cur1.execute("BEGIN OPTIMISTIC;")
    print("[Thread1] BEGIN OPTIMISTIC", flush=True)

    # update
    cur1.execute("UPDATE users SET name = 'conn1' WHERE id = 1;")
    print("[Thread1] UPDATED row → name='conn1'", flush=True)
    ...

    # thread1 try to commit.
    # error(s) will be raised if there is a conflict
    try:
        cur1.execute("UPDATE users SET name = 'conn1' WHERE id = 1;")
        print("[Thread1] UPDATED row → name='conn1'", flush=True)
        print("[Thread1] Try to commit", flush=True)

        cur1.execute("COMMIT;")
        print("[Thread1] Commit Successful", flush=True)

    except mysql.connector.Error as err:
        print("[Thread1] Conflict occurs: {}".format(err), flush=True)
        print("[Thread1] Retry", flush=True)

        try:
            cur1.execute("BEGIN OPTIMISTIC;")
            print("[Thread1 retry] BEGIN OPTIMISTIC", flush=True)

            cur1.execute("UPDATE users SET name = 'conn1' WHERE id = 1;")
            print("[Thread1 retry] UPDATED row → name='conn1'", flush=True)

            cur1.execute("COMMIT;")
            print("[Thread1 retry] Commit successful", flush=True)

        except mysql.connector.Error as err:
            print("[Thread1 retry] Conflict occurs: {}".format(err), flush=True)
  ```

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

+ 您可以使用以下命令运行乐观锁的演示代码。由于我们没有使用 `threading.Event` 来控制执行顺序，所以有许多可能的输出。我们在这里列出三个典型输出。在第一个示例输出中，`线程 2` 发生冲突并重试。在第二个示例输出中，`线程 1` 发生冲突。在第三个示例输出中，没有发生冲突。

+ 但是，在这种情况下不会发生事务锁等待。如果发生冲突，解决方案由您自行决定。在我们的演示中，将执行简单的重试。

  ```bash
  poetry run python mysql_connector_python_demo/lock_optimistic.py
  ```

  ```output
  [Thread1] BEGIN OPTIMISTIC
  [Thread1] UPDATED row → name='conn1'
  [Thread1] UPDATED row → name='conn1'
  [Thread1] Try to commit
  [Thread2] BEGIN OPTIMISTIC
  [Thread1] Commit Successful
  [Thread2] UPDATED row → name='conn2'
  [Thread2] Try to commit
  [Thread2] Conflict occurs: 9007 (HY000): Write conflict, txnStartTS=458156067333603352, conflictStartTS=458156067333603338, conflictCommitTS=458156067333603359, key=????, reason=Optimistic [try again later]
  [Thread2] Retry
  [Thread2 retry] BEGIN OPTIMISTIC
  [Thread2 retry] UPDATED row → name='conn2'
  [Thread2 retry] Commit successful
  Update Result: name is changed to conn2
  ```

  ```output
  [Thread2] BEGIN OPTIMISTIC
  [Thread2] UPDATED row → name='conn2'
  [Thread2] Try to commit
  [Thread1] BEGIN OPTIMISTIC
  [Thread2] Commit Successful
  [Thread1] UPDATED row → name='conn1'
  [Thread1] UPDATED row → name='conn1'
  [Thread1] Try to commit
  [Thread1] Conflict occurs: 9007 (HY000): Write conflict, txnStartTS=458156066350563402, conflictStartTS=458156066350563397, conflictCommitTS=458156066350563406, key=????, reason=Optimistic [try again later]
  [Thread1] Retry
  [Thread1 retry] BEGIN OPTIMISTIC
  [Thread1 retry] UPDATED row → name='conn1'
  [Thread1 retry] Commit successful
  Update Result: name is changed to conn1
  ```

  ```output
  [Thread2] BEGIN OPTIMISTIC
  [Thread2] UPDATED row → name='conn2'
  [Thread2] Try to commit
  [Thread2] Commit Successful
  [Thread1] BEGIN OPTIMISTIC
  [Thread1] UPDATED row → name='conn1'
  [Thread1] UPDATED row → name='conn1'
  [Thread1] Try to commit
  [Thread1] Commit Successful
  Update Result: name is changed to conn1
  ```

### 步骤 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 ${LAB:SERVERLESS_CLUSTER_DATABASE_NAME}
    ```

    ![chat2query.png 85%](https://lab-static.pingcap.com/images/2026/3/2/92fb25fbfdbbf592a6e6b2abd0b095584a06d477.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`。

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

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

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

{% endif %}
