Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update pessimistic transaction #3177

Merged
merged 14 commits into from
May 26, 2020
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added media/pessimistic-transaction-pipelining.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
73 changes: 53 additions & 20 deletions pessimistic-transaction.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,39 +7,45 @@ aliases: ['/docs-cn/dev/reference/transactions/transaction-pessimistic/']

# TiDB 悲观事务模型

v3.0.8 之前,TiDB 默认使用的乐观事务模式会导致事务提交时因为冲突而失败。为了保证事务的成功率,需要修改应用程序,加上重试的逻辑。悲观事务模式可以避免这个问题,应用程序无需添加重试逻辑,就可以正常执行
为了使 TiDB 的使用方式更加贴近传统数据库,降低用户迁移的成本,TiDB 自 v3.0 版本开始在乐观事务模型的基础上支持了悲观事务模型。本文将介绍 TiDB 悲观事务的相关特性
lilin90 marked this conversation as resolved.
Show resolved Hide resolved

## 悲观事务的使用方法
> **注意:**
>
> 自 v3.0.8 开始,新创建的 TiDB 集群默认使用悲观事务模型。但如果从 v3.0.7 版本及之前创建的集群升级到 >= v3.0.8 的版本,则不会改变默认的事务模型,即**只有新创建的集群才会默认使用悲观事务模型**。

进入悲观事务模式有以下三种方式:
## 事务模式的修改方法

- 执行 `BEGIN PESSIMISTIC;` 语句开启的事务,会进入悲观事务模式。
可以通过写成注释的形式 `BEGIN /*!90000 PESSIMISTIC */;` 来兼容 MySQL 语法。
TiDB 提供了 [tidb_txn_mode](/tidb-specific-system-variables.md#tidb_txn_mode) 系统变量用于设置事务模式,执行下面命令即可使整个集群所有新创建 session 执行的所有显示事务(即非 autocommit 的事务)进入悲观事务模式:
youjiali1995 marked this conversation as resolved.
Show resolved Hide resolved

- 执行 `set @@tidb_txn_mode = 'pessimistic';`,使这个 session 执行的所有显式事务(即非 autocommit 的事务)都会进入悲观事务模式。
{{< copyable "sql" >}}

- 执行 `set @@global.tidb_txn_mode = 'pessimistic';`,使之后整个集群所有新创建 session 执行的所有显示事务(即非 autocommit 的事务)都会进入悲观事务模式。
```sql
set @@global.tidb_txn_mode = 'pessimistic';
```

在配置了 `global.tidb_txn_mode` 为 `pessimistic` 之后,默认进入悲观事务模式,但是可以用以下三种方式使事务进入乐观事务模式
除此之外,还可以执行如下 SQL 显式开启悲观事务
youjiali1995 marked this conversation as resolved.
Show resolved Hide resolved

- 执行 `BEGIN OPTIMISTIC;` 语句开启的事务,会进入乐观事务模式。
可以通过写成注释的形式 `BEGIN /*!90000 OPTIMISTIC */;` 来兼容 MySQL 语法。
{{< copyable "sql" >}}

- 执行 `set @@tidb_txn_mode = 'optimistic';` 或 `set @@tidb_txn_mode = '';`,使当前的 session 执行的事务进入乐观事务模式。
```sql
BEGIN PESSIMISTIC;
```

- 执行 `set @@global.tidb_txn_mode = 'optimistic;'` 或 `set @@global.tidb_txn_mode = '';`,使之后整个集群所有新创建 session 执行的事务都进入乐观事务模式。
{{< copyable "sql" >}}

`BEGIN PESSIMISTIC;` 和 `BEGIN OPTIMISTIC;` 语句的优先级高于 `tidb_txn_mode` 系统变量。使用这两个语句开启的事务,会忽略系统变量,从而支持悲观、乐观事务混合使用。
```
BEGIN /*!90000 PESSIMISTIC */;
```

如果想要禁用悲观事务特性,可以修改 TiDB 配置文件,在 `[pessimistic-txn]` 类别下添加 `enable = false`
`BEGIN PESSIMISTIC;` 和 `BEGIN OPTIMISTIC;` 等语句的优先级高于 `tidb_txn_mode` 系统变量。使用这两个语句开启的事务,会忽略系统变量,从而支持悲观、乐观事务混合使用

## 悲观事务模式的行为

悲观事务的行为和 MySQL 基本一致(不一致之处详见[和 MySQL InnoDB 的差异](#和-mysql-innodb-的差异)):

- `SELECT FOR UPDATE` 会读取已提交的最新数据,并对读取到的数据加悲观锁。
- `SELECT FOR UPDATE` 会读取已提交的**最新**数据,并对读取到的数据加悲观锁。

- `UPDATE`、`DELETE` 和 `INSERT` 语句都会读取已提交的最新的数据来执行,并对修改的数据加悲观锁。
- `UPDATE`、`DELETE` 和 `INSERT` 语句都会读取已提交的**最新**的数据来执行,并对修改的数据加悲观锁。

- 当一行数据被加了悲观锁以后,其他尝试修改这一行的写事务会被阻塞,等待悲观锁的释放。

Expand All @@ -56,13 +62,14 @@ aliases: ['/docs-cn/dev/reference/transactions/transaction-pessimistic/']
- 通过设置 `innodb_lock_wait_timeout` 变量,设置等锁超时时间,等锁超时后返回兼容 MySQL 的错误码 `1205`。

- 支持 `FOR UPDATE NOWAIT` 语法,遇到锁时不会阻塞等锁,而是返回兼容 MySQL 的错误码 `3572`。

- 如果 `Point Get` 和 `Batch Point Get` 算子没有读到数据,依然会对给定的主键或者唯一键加锁,阻塞其他事务对相同主键唯一键加锁或者进行写入操作。

## 和 MySQL InnoDB 的差异

1. TiDB 使用 range 作为 WHERE 条件,执行 DML 和 `SELECT FOR UPDATE` 语句时不会阻塞范围内并发的 `INSERT` 语句的执行。

InnoDB 通过实现 gap lock,支持阻塞 range 内并发的 `INSERT` 语句的执行,其主要目的是为了支持 statement based binlog,因此有些业务会通过将隔离级别降低至 READ COMMITTED 来避免 gap lock 导致的并发性能问题。TiDB 不支持 gap lock,也就不需要付出相应的并发性能的代价。
InnoDB 通过实现 gap lock,支持阻塞 range 内并发的 `INSERT` 语句的执行,其主要目的是为了支持 statement based binlog,因此有些业务会通过将隔离级别降低至 Read Committed 来避免 gap lock 导致的并发性能问题。TiDB 不支持 gap lock,也就不需要付出相应的并发性能的代价。

2. TiDB 不支持 `SELECT LOCK IN SHARE MODE`。

Expand All @@ -74,12 +81,38 @@ aliases: ['/docs-cn/dev/reference/transactions/transaction-pessimistic/']

youjiali1995 marked this conversation as resolved.
Show resolved Hide resolved
4. `START TRANSACTION WITH CONSISTENT SNAPSHOT` 之后,MySQL 仍然可以读取到之后在其他事务创建的表,而 TiDB 不能。

5. autocommit 事务不支持悲观锁
5. autocommit 事务不支持悲观锁

所有自动提交的语句都不会加悲观锁,该类语句在用户侧感知不到区别,因为悲观事务的本质是把整个事务的重试变成了单个 DML 的重试,autocommit 事务即使在 TiDB 关闭重试时也会自动重试,效果和悲观事务相同。

自动提交的 select for update 语句也不会等锁。
6. 对语句中 `EMBEDDED SELECT` 读到的相关数据不会加锁。

6. 对语句中 `EMBEDDED SELECT` 读到的相关数据不会加锁。

## 隔离级别

TiDB 在悲观事务模式下支持了 2 种隔离级别:

1. 默认使用与 MySQL 行为相同的[可重复读隔离级别 (Repeatable Read)](/transaction-isolation-levels.md#可重复读隔离级别-repeatable-read)。

> **注意:**
>
> 在这种隔离级别下 DML 会基于已提交的最新数据来执行,行为与 MySQL 相同,但与 TiDB 乐观事务不同,请参考 [与 MySQL 可重复读隔离级别的区别](/transaction-isolation-levels.md#与-mysql-可重复读隔离级别的区别)。
youjiali1995 marked this conversation as resolved.
Show resolved Hide resolved

2. 使用 [SET TRANSACTION](/sql-statements/sql-statement-set-transaction.md) 可设置为 [读已提交隔离级别 (Read Committed)](/transaction-isolation-levels.md#读已提交隔离级别-read-committed)。
youjiali1995 marked this conversation as resolved.
Show resolved Hide resolved

## Pipelined 加锁流程

加悲观锁需要向 TiKV 写入数据,要经过 raft 提交并 apply 后才能返回,相比于乐观事务,不可避免的会增加部分延迟。为了降低加锁的开销,TiKV 实现了 pipelined 加锁流程:当数据满足加锁要求时,TiKV 立刻通知 TiDB 执行后面的请求,并异步写入悲观锁,从而降低大部分延迟,显著提升悲观事务的性能。但有较低概率悲观锁异步写入失败,可能会导致悲观事务提交失败。
youjiali1995 marked this conversation as resolved.
Show resolved Hide resolved

![Pipelined pessimistic lock](/media/pessimistic-transaction-pipelining.png)

该功能默认关闭,可修改 TiKV 配置启用:
youjiali1995 marked this conversation as resolved.
Show resolved Hide resolved

```toml
[pessimistic-txn]
pipelined = true
```

## 常见问题

Expand All @@ -93,4 +126,4 @@ aliases: ['/docs-cn/dev/reference/transactions/transaction-pessimistic/']

3. 悲观事务执行时间限制。

除了有事务执行时间不能超出 `tikv_gc_life_time` 的限制外,悲观事务的 TTL 有 10 分钟上限,所以执行时间超过 10 分钟的悲观事务有可能提交失败
在 v4.0 中,GC 已不会影响到正在运行的事务,但悲观事务的执行时间仍有上限,默认为 10 分钟,可通过 TiDB 配置文件 `[performance]` 类别下的 `max-txn-ttl` 修改