- 关于 TiDB
- 快速上手
- 应用开发
- 概览
- 快速开始
- 使用 TiDB Cloud (DevTier) 构建 TiDB 集群
- 使用 TiDB 的增删改查 SQL
- TiDB 的简单 CRUD 应用程序
- 示例程序
- 连接到 TiDB
- 数据库模式设计
- 数据写入
- 数据读取
- 事务
- 优化 SQL 性能
- 故障诊断
- 引用文档
- 云原生开发环境
- 部署标准集群
- 数据迁移
- 运维操作
- 监控与告警
- 故障诊断
- 性能调优
- 优化手册
- 配置优化
- SQL 性能调优
- SQL 性能调优概览
- 理解 TiDB 执行计划
- SQL 优化流程
- 控制执行计划
- 教程
- 同城多中心部署
- 两地三中心部署
- 同城两中心部署
- 读取历史数据
- 使用 Stale Read 功能读取历史数据(推荐)
- 使用系统变量
tidb_snapshot
读取历史数据
- 最佳实践
- Placement Rules 使用文档
- Load Base Split 使用文档
- Store Limit 使用文档
- TiDB 工具
- 功能概览
- 适用场景
- 工具下载
- TiUP
- 文档地图
- 概览
- 术语及核心概念
- TiUP 组件管理
- FAQ
- 故障排查
- TiUP 命令参考手册
- 命令概览
- TiUP 命令
- TiUP Cluster 命令
- TiUP Cluster 命令概览
- tiup cluster audit
- tiup cluster check
- tiup cluster clean
- tiup cluster deploy
- tiup cluster destroy
- tiup cluster disable
- tiup cluster display
- tiup cluster edit-config
- tiup cluster enable
- tiup cluster help
- tiup cluster import
- tiup cluster list
- tiup cluster patch
- tiup cluster prune
- tiup cluster reload
- tiup cluster rename
- tiup cluster replay
- tiup cluster restart
- tiup cluster scale-in
- tiup cluster scale-out
- tiup cluster start
- tiup cluster stop
- tiup cluster template
- tiup cluster upgrade
- TiUP DM 命令
- TiUP DM 命令概览
- tiup dm audit
- tiup dm deploy
- tiup dm destroy
- tiup dm disable
- tiup dm display
- tiup dm edit-config
- tiup dm enable
- tiup dm help
- tiup dm import
- tiup dm list
- tiup dm patch
- tiup dm prune
- tiup dm reload
- tiup dm replay
- tiup dm restart
- tiup dm scale-in
- tiup dm scale-out
- tiup dm start
- tiup dm stop
- tiup dm template
- tiup dm upgrade
- TiDB 集群拓扑文件配置
- DM 集群拓扑文件配置
- TiUP 镜像参考指南
- TiUP 组件文档
- PingCAP Clinic 诊断服务 (Technical Preview)
- TiDB Operator
- Dumpling
- TiDB Lightning
- TiDB Data Migration
- 关于 Data Migration
- 快速开始
- 部署 DM 集群
- 入门指南
- 进阶教程
- 运维管理
- 参考手册
- 使用示例
- 异常解决
- 版本发布历史
- Backup & Restore (BR)
- TiDB Binlog
- TiCDC
- sync-diff-inspector
- TiSpark
- 参考指南
- 架构
- 监控指标
- 安全加固
- 权限
- SQL
- SQL 语言结构和语法
- SQL 语句
ADD COLUMN
ADD INDEX
ADMIN
ADMIN CANCEL DDL
ADMIN CHECKSUM TABLE
ADMIN CHECK [TABLE|INDEX]
ADMIN SHOW DDL [JOBS|QUERIES]
ADMIN SHOW TELEMETRY
ALTER DATABASE
ALTER INDEX
ALTER INSTANCE
ALTER PLACEMENT POLICY
ALTER TABLE
ALTER USER
ANALYZE TABLE
BACKUP
BEGIN
CHANGE COLUMN
CHANGE DRAINER
CHANGE PUMP
COMMIT
CREATE [GLOBAL|SESSION] BINDING
CREATE DATABASE
CREATE INDEX
CREATE PLACEMENT POLICY
CREATE ROLE
CREATE SEQUENCE
CREATE TABLE LIKE
CREATE TABLE
CREATE USER
CREATE VIEW
DEALLOCATE
DELETE
DESC
DESCRIBE
DO
DROP [GLOBAL|SESSION] BINDING
DROP COLUMN
DROP DATABASE
DROP INDEX
DROP PLACEMENT POLICY
DROP ROLE
DROP SEQUENCE
DROP STATS
DROP TABLE
DROP USER
DROP VIEW
EXECUTE
EXPLAIN ANALYZE
EXPLAIN
FLASHBACK TABLE
FLUSH PRIVILEGES
FLUSH STATUS
FLUSH TABLES
GRANT <privileges>
GRANT <role>
INSERT
KILL [TIDB]
LOAD DATA
LOAD STATS
MODIFY COLUMN
PREPARE
RECOVER TABLE
RENAME INDEX
RENAME TABLE
REPLACE
RESTORE
REVOKE <privileges>
REVOKE <role>
ROLLBACK
SELECT
SET DEFAULT ROLE
SET [NAMES|CHARACTER SET]
SET PASSWORD
SET ROLE
SET TRANSACTION
SET [GLOBAL|SESSION] <variable>
SHOW [BACKUPS|RESTORES]
SHOW ANALYZE STATUS
SHOW [GLOBAL|SESSION] BINDINGS
SHOW BUILTINS
SHOW CHARACTER SET
SHOW COLLATION
SHOW [FULL] COLUMNS FROM
SHOW CONFIG
SHOW CREATE PLACEMENT POLICY
SHOW CREATE SEQUENCE
SHOW CREATE TABLE
SHOW CREATE USER
SHOW DATABASES
SHOW DRAINER STATUS
SHOW ENGINES
SHOW ERRORS
SHOW [FULL] FIELDS FROM
SHOW GRANTS
SHOW INDEX [FROM|IN]
SHOW INDEXES [FROM|IN]
SHOW KEYS [FROM|IN]
SHOW MASTER STATUS
SHOW PLACEMENT
SHOW PLACEMENT FOR
SHOW PLACEMENT LABELS
SHOW PLUGINS
SHOW PRIVILEGES
SHOW [FULL] PROCESSSLIST
SHOW PROFILES
SHOW PUMP STATUS
SHOW SCHEMAS
SHOW STATS_HEALTHY
SHOW STATS_HISTOGRAMS
SHOW STATS_META
SHOW STATUS
SHOW TABLE NEXT_ROW_ID
SHOW TABLE REGIONS
SHOW TABLE STATUS
SHOW [FULL] TABLES
SHOW [GLOBAL|SESSION] VARIABLES
SHOW WARNINGS
SHUTDOWN
SPLIT REGION
START TRANSACTION
TABLE
TRACE
TRUNCATE
UPDATE
USE
WITH
- 数据类型
- 函数与操作符
- 聚簇索引
- 约束
- 生成列
- SQL 模式
- 表属性
- 事务
- 垃圾回收 (GC)
- 视图
- 分区表
- 临时表
- 缓存表
- 字符集和排序
- Placement Rules in SQL
- 系统表
mysql
- INFORMATION_SCHEMA
- Overview
ANALYZE_STATUS
CLIENT_ERRORS_SUMMARY_BY_HOST
CLIENT_ERRORS_SUMMARY_BY_USER
CLIENT_ERRORS_SUMMARY_GLOBAL
CHARACTER_SETS
CLUSTER_CONFIG
CLUSTER_HARDWARE
CLUSTER_INFO
CLUSTER_LOAD
CLUSTER_LOG
CLUSTER_SYSTEMINFO
COLLATIONS
COLLATION_CHARACTER_SET_APPLICABILITY
COLUMNS
DATA_LOCK_WAITS
DDL_JOBS
DEADLOCKS
ENGINES
INSPECTION_RESULT
INSPECTION_RULES
INSPECTION_SUMMARY
KEY_COLUMN_USAGE
METRICS_SUMMARY
METRICS_TABLES
PARTITIONS
PLACEMENT_POLICIES
PROCESSLIST
REFERENTIAL_CONSTRAINTS
SCHEMATA
SEQUENCES
SESSION_VARIABLES
SLOW_QUERY
STATISTICS
TABLES
TABLE_CONSTRAINTS
TABLE_STORAGE_STATS
TIDB_HOT_REGIONS
TIDB_HOT_REGIONS_HISTORY
TIDB_INDEXES
TIDB_SERVERS_INFO
TIDB_TRX
TIFLASH_REPLICA
TIKV_REGION_PEERS
TIKV_REGION_STATUS
TIKV_STORE_STATUS
USER_PRIVILEGES
VIEWS
METRICS_SCHEMA
- UI
- CLI
- 命令行参数
- 配置文件参数
- 系统变量
- 存储引擎
- 遥测
- 错误码
- 通过拓扑 label 进行副本调度
- 常见问题解答 (FAQ)
- 版本发布历史
- 术语表
TiDB 悲观事务模式
为了使 TiDB 的使用方式更加贴近传统数据库,降低用户迁移的成本,TiDB 自 v3.0 版本开始在乐观事务模型的基础上支持了悲观事务模式。本文将介绍 TiDB 悲观事务的相关特性。
自 v3.0.8 开始,新创建的 TiDB 集群默认使用悲观事务模式。但如果从 v3.0.7 版本及之前创建的集群升级到 >= v3.0.8 的版本,则不会改变默认的事务模式,即只有新创建的集群才会默认使用悲观事务模式。
事务模式的修改方法
你可以使用 tidb_txn_mode
系统变量设置事务模式。执行以下命令,即可使整个集群中所有新创建 session 执行的所有显示事务(即非 autocommit 的事务)进入悲观事务模式:
SET GLOBAL tidb_txn_mode = 'pessimistic';
除此之外,还可以执行以下 SQL 语句显式地开启悲观事务:
BEGIN PESSIMISTIC;
BEGIN /*T! PESSIMISTIC */;
BEGIN PESSIMISTIC;
和 BEGIN OPTIMISTIC;
等语句的优先级高于 tidb_txn_mode
系统变量。使用这两个语句开启的事务,会忽略系统变量,从而支持悲观、乐观事务混合使用。
悲观事务模式的行为
悲观事务的行为和 MySQL 基本一致(不一致之处详见和 MySQL InnoDB 的差异):
悲观事务中引入快照读和当前读的概念:
快照读是一种不加锁读,读的是该事务开始时刻前已提交的版本。
SELECT
语句中的读是快照读。当前读是一种加锁读,读取的是最新已提交的版本,
UPDATE
、DELETE
、INSERT
、SELECT FOR UPDATE
语句中的读是当前读。以下示例是对快照读和当前读的详细说明:
session 1 session 2 session 3 CREATE TABLE t (a INT); INSERT INTO T VALUES(1); BEGIN PESSIMISTIC; UPDATE t SET a = a + 1; BEGIN PESSIMISTIC; SELECT * FROM t; -- 使用快照读,读取本事务开始前已提交的版本,返回(a=1) BEGIN PESSIMISTIC; SELECT * FROM t FOR UPDATE; -- 使用当前读,等锁 COMMIT; -- 释放锁,session 3 的 SELECT FOR UPDATE 操作获得锁,使用当前读,读到最新已提交的版本 (a=2) SELECT * FROM t; -- 使用快照读,读取本事务开始前已提交的版本,返回(a=1)
悲观锁会在事务提交或回滚时释放。其他尝试修改这一行的写事务会被阻塞,等待悲观锁的释放。其他尝试读取这一行的事务不会被阻塞,因为 TiDB 采用多版本并发控制机制 (MVCC)。
如果多个事务尝试获取各自的锁,会出现死锁,并被检测器自动检测到。其中一个事务会被随机终止掉并返回兼容 MySQL 的错误码
1213
。通过
innodb_lock_wait_timeout
变量,设置事务等锁的超时时间(默认值为50
,单位为秒)。等锁超时后返回兼容 MySQL 的错误码1205
。如果多个事务同时等待同一个锁释放,会大致按照事务start ts
顺序获取锁。乐观事务和悲观事务可以共存,事务可以任意指定使用乐观模式或悲观模式来执行。
支持
FOR UPDATE NOWAIT
语法,遇到锁时不会阻塞等锁,而是返回兼容 MySQL 的错误码3572
。如果
Point Get
和Batch Point Get
算子没有读到数据,依然会对给定的主键或者唯一键加锁,阻塞其他事务对相同主键唯一键加锁或者进行写入操作。支持
FOR UPDATE OF TABLES
语法,对于存在多表 join 的语句,只对OF TABLES
中包含的表关联的行进行悲观锁加锁操作。
和 MySQL InnoDB 的差异
有些
WHERE
子句中使用了 range,TiDB 在执行这类 DML 语句和SELECT FOR UPDATE
语句时,不会阻塞 range 内并发的 DML 语句的执行。举例:
CREATE TABLE t1 ( id INT NOT NULL PRIMARY KEY, pad1 VARCHAR(100) ); INSERT INTO t1 (id) VALUES (1),(5),(10);
BEGIN /*T! PESSIMISTIC */; SELECT * FROM t1 WHERE id BETWEEN 1 AND 10 FOR UPDATE;
BEGIN /*T! PESSIMISTIC */; INSERT INTO t1 (id) VALUES (6); -- 仅 MySQL 中出现阻塞。 UPDATE t1 SET pad1='new value' WHERE id = 5; -- MySQL 和 TiDB 处于等待阻塞状态。
产生这一行为是因为 TiDB 当前不支持 gap locking(间隙锁)。
TiDB 不支持
SELECT LOCK IN SHARE MODE
。使用这个语句执行的时候,效果和没有加锁是一样的,不会阻塞其他事务的读写。
DDL 可能会导致悲观事务提交失败。
MySQL 在执行 DDL 语句时,会被正在执行的事务阻塞住,而在 TiDB 中 DDL 操作会成功,造成悲观事务提交失败:
ERROR 1105 (HY000): Information schema is changed. [try again later]
。TiDB 事务执行过程中并发执行TRUNCATE TABLE
语句,可能会导致事务报错table doesn't exist
。START TRANSACTION WITH CONSISTENT SNAPSHOT
之后,MySQL 仍然可以读取到之后在其他事务创建的表,而 TiDB 不能。autocommit 事务优先采用乐观事务提交。
使用悲观事务模式时,autocommit 事务首先尝试使用开销更小的乐观事务模式提交。如果发生了写冲突,重试时才会使用悲观事务提交。所以
tidb_retry_limit = 0
时,autocommit 事务遇到写冲突仍会报Write Conflict
错误。自动提交的
SELECT FOR UPDATE
语句不会等锁。对语句中
EMBEDDED SELECT
读到的相关数据不会加锁。垃圾回收 (GC) 不会影响到正在执行的事务,但悲观事务的执行时间仍有上限,默认为 1 小时,可通过 TiDB 配置文件
[performance]
类别下的max-txn-ttl
修改。
隔离级别
TiDB 在悲观事务模式下支持了 2 种隔离级别:
默认使用与 MySQL 行为相同的可重复读隔离级别 (Repeatable Read)。
注意在这种隔离级别下,DML 操作会基于已提交的最新数据来执行,行为与 MySQL 相同,但与 TiDB 乐观事务不同,请参考与 MySQL 可重复读隔离级别的区别。
使用
SET TRANSACTION
语句可将隔离级别设置为读已提交隔离级别 (Read Committed)。
悲观事务提交流程
TiDB 悲观锁复用了乐观锁的两阶段提交逻辑,重点在 DML 执行时做了改造。
在两阶段提交之前增加了 Acquire Pessimistic Lock 阶段,简要步骤如下。
- (同乐观锁)TiDB 收到来自客户端的 begin 请求,获取当前版本号作为本事务的 StartTS。
- TiDB 收到来自客户端的更新数据的请求:TiDB 向 TiKV 发起加悲观锁请求,该锁持久化到 TiKV。
- (同乐观锁)客户端发起 commit,TiDB 开始执行与乐观锁一样的两阶段提交。
相关细节本节不再赘述,详情可阅读 TiDB 悲观锁实现原理。
Pipelined 加锁流程
加悲观锁需要向 TiKV 写入数据,要经过 Raft 提交并 apply 后才能返回,相比于乐观事务,不可避免的会增加部分延迟。为了降低加锁的开销,TiKV 实现了 pipelined 加锁流程:当数据满足加锁要求时,TiKV 立刻通知 TiDB 执行后面的请求,并异步写入悲观锁,从而降低大部分延迟,显著提升悲观事务的性能。但当 TiKV 出现网络隔离或者节点宕机时,悲观锁异步写入有可能失败,从而产生以下影响:
无法阻塞修改相同数据的其他事务。如果业务逻辑依赖加锁或等锁机制,业务逻辑的正确性将受到影响。
有较低概率导致事务提交失败,但不会影响事务正确性。
如果业务逻辑依赖加锁或等锁机制,或者即使在集群异常情况下也要尽可能保证事务提交的成功率,应关闭 pipelined 加锁功能。
该功能默认开启,可修改 TiKV 配置关闭:
[pessimistic-txn]
pipelined = false
若集群是 v4.0.9 及以上版本,也可通过在线修改 TiKV 配置功能动态关闭该功能:
set config tikv pessimistic-txn.pipelined='false';
内存悲观锁
TiKV 在 v6.0.0 中引入了内存悲观锁功能。开启内存悲观锁功能后,悲观锁通常只会被存储在 Region leader 的内存中,而不会将锁持久化到磁盘,也不会通过 Raft 协议将锁同步到其他副本,因此可以大大降低悲观事务加锁的开销,提升悲观事务的吞吐并降低延迟。
当内存悲观锁占用的内存达到 Region 或节点的阈值时,加悲观锁会回退为使用 pipelined 加锁流程。当 Region 发生合并或 leader 迁移时,为避免悲观锁丢失,TiKV 会将内存悲观锁写入磁盘并同步到其他副本。
内存悲观锁实现了和 pipelined 加锁类似的表现,即集群无异常时不影响加锁表现,但当 TiKV 出现网络隔离或者节点宕机时,事务加的悲观锁可能丢失。
如果业务逻辑依赖加锁或等锁机制,或者即使在集群异常情况下也要尽可能保证事务提交的成功率,应关闭内存悲观锁功能。
该功能默认开启。如要关闭,可修改 TiKV 配置:
[pessimistic-txn]
in-memory = false
也可通过在线修改 TiKV 配置功能动态关闭该功能:
set config tikv pessimistic-txn.in-memory='false';