21.事务和锁(重点)

1.事务

1.什么是事务

事务是把⼀组SQL语句打包成为⼀个整体,在这组SQL的执⾏过程中,要么全部成功,要么全部失
败,这组SQL语句可以是⼀条也可以是多条。
示例:

sql 复制代码
# ================账⼾表====================
CREATE TABLE `account` (
 `id` bigint PRIMARY KEY AUTO_INCREMENT, 
 `name` varchar(255) NOT NULL, # 姓名
 `balance` decimal(10, 2) NOT NULL # 余额
);
INSERT INTO account(`name`, balance) VALUES('张三', 1000);
INSERT INTO account(`name`, balance) VALUES('李四', 1000);
# ================更新操作===================
# 张三余额减少100
UPDATE account set balance = balance - 100 where name = '张三';
# 李四余额增加100
UPDATE account set balance = balance + 100 where name = '李四';

如果转账成功,应该有以下结果:

  1. 张三的账⼾余额减少 100 ,变成 900 ,李四的账⼾余额增加了 100 ,变成 1100 ,不能出现张
    三的余额减少⽽李四的余额没有增加的情况;
  2. 张三和李四在发⽣转账前后的总额不变,也就是说转账前张三和李四的余额总数为
    1000+1000=2000 ,转账后他们的余额总数为 900+1100=2000 ;
  3. 转账后的余额结果应当保存到存储介质中,以便以后读取;
  4. 还有⼀点需要要注意,在转账的处理过程中张三和李四的余额不能因其他的转账事件⽽受到⼲扰;以上这四点在事务的整个执⾏过程中必须要得到保证,这也就是事务的 ACID 特性
    • Atomicity (原⼦性):⼀个事务中的所有操作,要么全部成功,要么全部失败,不会出现只执
    ⾏了⼀半的情况,如果事务在执⾏过程中发⽣错误,会回滚( Rollback )到事务开始前的状
    态,就像这个事务从来没有执⾏过⼀样;
    • Consistency (⼀致性):在事务开始之前和事务结束以后,数据库的完整性不会被破坏。这表
    ⽰写⼊的数据必须完全符合所有的预设规则,包括数据的精度、关联性以及关于事务执⾏过程中服
    务器崩溃后如何恢复;
    • Isolation (隔离性):数据库允许多个并发事务同时对数据进⾏读写和修改,隔离性可以防⽌多
    个事务并发执⾏时由于交叉执⾏⽽导致数据的不⼀致。事务可以指定不同的隔离级别,以权衡在不
    同的应⽤场景下数据库性能和安全。
    • Durability (持久性):事务处理结束后,对数据的修改将永久的写⼊存储介质,即便系统故障
    也不会丢失。

2.为什么要使用事务

使用事务的核心目的是保证数据库操作的安全性、一致性和可靠性,尤其在多步操作或并发场景下,避免因异常(如系统崩溃、网络中断、操作错误)导致的数据混乱。

1. 防止 "部分操作成功、部分失败" 的中间状态(保证原子性)

2. 避免并发操作导致的数据冲突(保证隔离性)

3. 确保已完成的操作永久生效(保证持久性)

4. 维持业务规则的一致性(保证一致性)

3.怎么使用事务


• 通过以下语句可以完成对事务的控制:
◦ START TRANSACTION 或 BEGIN 开始⼀个新的事务;
◦ COMMIT 提交当前事务,并对更改持久化保存;
◦ ROLLBACK 回滚当前事务,取消其更改;
◦ SET autocommit 禁⽤或启⽤当前会话的默认⾃动提交模式, autocommit 是⼀个系统变
量可以通过选项指定也可以通过命令⾏设置 --autocommit[={OFF|ON}]
• 演⽰开启⼀个事务,执⾏修改后并回滚

sql 复制代码
# 开始事务
mysql> start transaction;

# 在修改之前查看表中的数据
mysql> select * from account;
+----+------+---------+
| id | name | balance |
+----+------+---------+
| 1 | 张三 | 1000.00 |
| 2 | 李四 | 1000.00 |
+----+------+---------+
2 rows in set (0.00 sec)

# 张三余额减少100
mysql> UPDATE account set balance = balance - 100 where name = '张三';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0

# 李四余额增加100
mysql> UPDATE account set balance = balance + 100 where name = '李四';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0

# 在修改之后,提交之前查看表中的数据,余额已经被修改
mysql> select * from account;
+----+------+---------+
| id | name | balance |
+----+------+---------+
| 1 | 张三 | 900.00 |
| 2 | 李四 | 1100.00 |
+----+------+---------+
2 rows in set (0.00 sec)

# 回滚事务
mysql> rollback;
Query OK, 0 rows affected (0.00 sec)

# 再查询发现修改没有⽣效
mysql> select * from account;
+----+------+---------+
| id | name | balance |
+----+------+---------+
| 1 | 张三 | 1000.00 |
| 2 | 李四 | 1000.00 |
+----+------+---------+
2 rows in set (0.00 sec)

若修改完数据后,这时提交事务mysql> commit;
再查询发现数据已被修改,说明数据已经持久化到磁盘
• 默认情况下MySQL启⽤事务⾃动提交,也就是说每个语句都是⼀个事务,就像被 START
TRANSACTION 和 COMMIT 包裹⼀样,不能使⽤ ROLLBACK 来撤销执⾏结果;但是如果在语句
执⾏期间发⽣错误,则⾃动回滚;
• 通过 SET autocommit 设置⾃动与⼿动提交

sql 复制代码
# 查看当前的事务提交模式
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON | # ON表⽰⾃动提交模式
+---------------+-------+
1 row in set, 1 warning (0.02 sec)

# 设置为⼿动提交(禁⽤⾃动提交)
mysql> SET AUTOCOMMIT=0; # ⽅式⼀
mysql> SET AUTOCOMMIT=OFF; # ⽅式⼆

# 再次查看事务提交模式
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | OFF | # OFF表⽰关闭⾃动提交,此时转为⼿动提交
+---------------+-------+
1 row in set, 1 warning (0.00 sec)

注意:只要使⽤ START TRANSACTION 或 BEGIN 开启事务,必须要通过 COMMIT 提交才会持
久化,与是否设置 SET autocommit ⽆关

2.InnoDB 和 ACID 模型

ACID模型是⼀组数据库设计原则,强调业务数据的可靠性,MySQL的InnoDB存储引擎严格遵循
ACID模型,不会因为软件崩溃和硬件故障等异常导致数据的不完整。
• Atomicity(原⼦性):原⼦性⽅⾯主要涉及InnoDB的事务开启与提交,我们之前做过详细讲解与回

◦ 设置 autocommit[={OFF|ON}] 系统变量,开启和禁⽤事务是否⾃动提交.
◦ 使⽤ START TRANSACTION 或 BEGIN TRANSACTION 语句开启事务;
◦ 使⽤ COMMIT 语句提交事务;
◦ 使⽤ ROLLBACK 语句回滚事务。
• Consistency(⼀致性):⼀致性主要涉及InnoDB内部对于崩溃时数据保护的相关处理,相关特性包
括:
◦ InnoDB 存储引擎的双写缓冲区 doublewrite buffer ;
◦ InnoDB 存储引擎的崩溃恢复
• Isolation(隔离性):隔离⽅⾯主要涉及应⽤于每个事务的隔离级别,相关特性包括:
◦ 通过 SET TRANSACTION 语句设置事务的隔离级别;
◦ InnoDB 存储引擎的锁,锁可以在 INFORMATION_SCHEMA 系统库和 Performance
Schema 系统库中的 data_locks 和 data_lock_waits 表查看
• Durability(持久性):持久性涉及MySQL与特定硬件配置的交互,可能性取决于CPU、⽹络和存储
设备的性能,由于硬件环境⽐较复杂,所以⽆法提供固定的操作指南,只能根据实际环境进⾏测试
得到最佳的性能,相关特性包括:
◦ InnoDB 存储引擎的双写缓冲区 doublewrite buffer ;
◦ innodb_flush_log_at_trx_commit 系统变量的设置;
◦ sync_binlog 系统变量的设置;
◦ innodb_file_per_table 系统变量的设置;
◦ 存储设备(如磁盘驱动器、SSD或RAID磁盘阵列)中的写缓冲区;
◦ 存储设备中由电池⽀持的缓存。
◦ 运⾏MySQL的操作系统,特别是对 fsync() 系统调⽤的⽀持;
◦ 不间断电源UPS (uninterruptible power supply),保护所有运⾏MySQL服务器和数据存储设备
的电⼒供应;
◦ 备份策略,例如备份的频率和类型,以及备份保留周期;
◦ 分布式环境中数据中⼼之间的⽹络连接。
• 需要重点说明的是,事务最终要保证数据的可靠和⼀致,也就是说 ACID 中的Consistency(⼀致
性)是最终的⽬的,那么当事务同时满⾜了Atomicity(原⼦性),Isolation(隔离性)和Durability(持
久性)时,也就实现了⼀致性。

3.如何实现原子性

• 在⼀个事务的执⾏过程中,如果多条DML语句顺利执⾏,那么结果最终会写⼊数据库;如果在事务的执⾏过程中,其中⼀条DML语句出现异常,导致后⾯的语句⽆法继续执⾏或即使继续执⾏也会导致数据不完整、不⼀致,这时前⾯执⾏的语句已经对数据做了修改,如果要保证⼀致性,就需要对之前的修改做撤销操作,这个撤销操作称为回滚 rollback


• 那么回滚操作是如何实现的呢?回滚过程中依据的是什么呢?在事务执⾏每个DML之前,把原始数据记录在⼀个⽇志⾥,做为回滚的依据,这个⽇志称为 Undo Log (回滚⽇志或撤销⽇志),在不考虑缓存和刷盘的条件下,执⾏过程如下所⽰:


• 当需要回滚操作时,MySQL根据操作类型,在Insert Undo链或Update Undo链中读取相应的⽇志
记录,并反向执⾏修改,使数据还原,完成回滚。
• 通过 Undo Log 实现了数据的回滚操作,这时就可以保证在事务成功的时候全部的SQL语句都执
⾏成功,在事务失败的时候全部的SQL语句都执⾏失败,实现在原⼦性。

4.如何实现持久性

提交的事务要把数据写入(持久化到)存储介质,⽐如磁盘。在正常情况下⼤多没有问题,可是在服
务器崩溃或突然断电的情况下,⼀个事务中的多个修改操作,只有⼀部分写⼊了数据⽂件,⽽另⼀部分没有写⼊,如果不做针对处理的话,就会造成数据的丢失,从⽽导致数据不完整,也就不能保证⼀致性。
在真正写⼊数据⽂件之前,MySQL会把事务中的所有DML操作以⽇志的形式记录下来,以便在服
务器下次启动的时候进⾏恢复操作,恢复操作的过程就是把⽇志中没有写到数据⽂件的记录重新执⾏⼀遍,保证所有的需要保存的数据都持久化到存储介质中,我们把这个⽇志称为 Redo Log (重做日志) ;⽣成重做⽇志是保证数据⼀致性的重要环节。在持久化的处理过程中,还包括
缓冲池

Doublewrite Buffer (双写缓冲区)Binary Log **(⼆进制日志)**等

5.隔离性实现原理

事务的隔离级别
事务具有隔离性,那么如何实现事务之间的隔离?隔离到什么程度?如何保证数据安全的同时也
要兼顾性能?这都是要思考的问题。
在过多线程技术中,我们都知道在并发执⾏的过程中,多个线程对同⼀个共享变量进⾏修改
时,在不加限制的情况下会出现线程安全问题,我们解决线程安全问题时,⼀般的做法是通过对修改 操作进⾏加锁;同理,多个事务在对同⼀个表中的同⼀条数据进⾏修改时,如果要实现事务间的隔离也可以通过锁来完成,在MySQL中常⻅的锁包括:读锁,写锁,⾏锁,间隙锁,Next-Key锁等,不同的锁策略联合多版本并发控制可以实现事务间不同程度的隔离,称为事务的隔离级别;不同的隔离级别在性能和安全⽅⾯做了取舍,有的隔离级别注重并发性,有的注重安全性,有的则是并发和安全适中;在MySQL的InnoDB引擎中事务的隔离级别有四种,分别是:
• READ UNCOMMITTED ,读未提交
• READ COMMITTED ,读已提交
• REPEATABLE READ ,可重复读(默认)
• SERIALIZABLE ,串⾏化
通过以上的介绍我们了解到事务的隔离级别是通过锁策略 联合多版本并发控制实现的

锁信息
锁的信息包括锁的请求(申请),锁的持有以及阻塞状态等等,都保存在 performance_schema
库的 data_locks 表中,可以通过以下⽅式查看:

sql 复制代码
mysql> SELECT * FROM performance_schema.data_locks\G
*************************** 1. row ***************************
 ENGINE: INNODB # 持有或请求锁的存储引擎
 ENGINE_LOCK_ID: 139664434886512:1059:139664350547912 # 存储引擎持有或请求
的锁的ID
ENGINE_TRANSACTION_ID: 2569 # 请求锁的事务对应的存储引擎内部ID
 THREAD_ID: 46 # 创建锁的会话的线程ID
 EVENT_ID: 12 # 请求锁的事件ID
 OBJECT_SCHEMA: test_db # 锁定表所在的数据库
 OBJECT_NAME: account # 被锁定表的名称
 PARTITION_NAME: NULL # 锁定分区的名称; 没有使⽤表分区时为NULL
 SUBPARTITION_NAME: NULL # 锁定⼦分区的名称; 没有使⽤⼦分区时为NULL
 INDEX_NAME: NULL # 锁定索引的名称; 没有使⽤索引时为NULL
OBJECT_INSTANCE_BEGIN: 139664350547912 # 锁在内存中的地址
 LOCK_TYPE: TABLE # 锁的类型
 LOCK_MODE: IX # 锁的模式,表⽰如何请求锁
 LOCK_STATUS: GRANTED # 锁请求的状态; GRANTED(持有),WAITING(等待)
 LOCK_DATA: NULL # 与锁相关的数据(如果有)
*************************** 2. row ***************************
 ENGINE: INNODB
 ENGINE_LOCK_ID: 139664434886512:2:4:1:139664350544872
ENGINE_TRANSACTION_ID: 2569
 THREAD_ID: 46
 EVENT_ID: 12
 OBJECT_SCHEMA: test_db 
 OBJECT_NAME: account
PARTITION_NAME: NULL
 SUBPARTITION_NAME: NULL
 INDEX_NAME: GEN_CLUST_INDEX
OBJECT_INSTANCE_BEGIN: 139664350544872
 LOCK_TYPE: RECORD # 锁的类型
 LOCK_MODE: X # 锁的模式,表⽰如何请求锁
 LOCK_STATUS: GRANTED # 锁请求的状态; 
 LOCK_DATA: supremum pseudo-record

• 锁类型
锁类型依赖于存储引擎,在InnoDB存储引擎中按照锁的粒度分为,⾏级锁 RECORD 和表级锁
TABLE :
◦ ⾏级锁也叫⾏锁,是对表中的某些具体的数据⾏进⾏加锁;
◦ 表级锁也叫表锁,是对整个数据表进⾏加锁。
在之前版本的BDB存储引擎中还⽀持⻚级锁,锁定的是⼀个数据⻚,MySQL8中没有⻚级锁
• 锁模式
锁模式,⽤来描述如何请求(申请)锁,分为共享锁(S)、独占锁(X)、意向共享锁(IS)、意向独占锁
(IX)、记录锁、间隙锁、Next-Key锁、AUTO-INC 锁、空间索引的谓词锁等

1.共享锁和独占锁 - Shared and Exclusive Locks

InnoDB实现了标准的⾏级锁,分为两种分别是共享锁(S锁)和独占锁(X锁),独占锁也称为排他锁。
• 共享锁(S锁):允许持有该锁的事务读取表中的⼀⾏记录,同时允许其他事务在锁定⾏上加另⼀个
共享锁并读取 被锁定的对象,但不能对其进⾏写操作;
• 独占锁(X锁):允许持有该锁的事务对数据⾏进⾏更新或删除,同时不论其他事务对锁定⾏进⾏读
取或修改都不允许对锁定⾏进⾏加锁;

sql 复制代码
# 对查询结果集中的每⾏数据都加共享锁
select * from account where id < 2 for share; # MYSQL8.0(推荐)
select * from account where id < 2 LOCK IN SHARE MODE; # 8.0以及之前版本

# 对查询结果集中的每⾏数据都加排他锁
select * from account where id = 1 for update;

# 可以使⽤以下SQL在监视器中查看锁信息
show engine innodb status\G

• 如果事务T1持有R⾏上的共享锁(S),那么事务T2请求R⾏上的锁时会有如下处理:
◦ T2请求S锁会⽴即被授予,此时T1和T2都对R⾏持有S锁;
◦ T2请求X锁不能⽴即被授予,阻塞到T1释放持有的锁
• 如果事务T1持有R⾏上的独占锁(X),那么T2请求R⾏上的任意类型锁都不能⽴即被授予,事务T2必须等待事务T1释放R⾏上的锁。
TIPS:
读锁是共享锁的⼀种实现,写锁是排他锁的⼀种实现

2.意向锁 - Intention Locks

• InnoDB⽀持多粒度锁,允许⾏锁和表锁共存;
• InnoDB使⽤意向锁实现多粒度级别的锁,意向锁是表级别的锁,它并不是真正意义上的加锁,⽽
只是在 data_locks 中记录事务以后要对表中的哪⼀⾏加哪种类型的锁(共享锁或排他锁),意向
锁分为两种:
◦ 意向共享锁(IS):表⽰事务打算对表中的单个⾏设置共享锁。
◦ 意向排他锁(IX):表⽰事务打算对表中的单个⾏设置排他锁。
• 在获取意向锁时有如下协议:
◦ 在事务获得表中某⼀⾏的共享锁(S)之前,它必须⾸先获得该表上的IS锁或更强的锁。
◦ 在事务获得表中某⼀⾏的排他锁(X)之前,它必须⾸先获得该表上的IX锁。
• 意向锁可以提⾼加锁的性能,在真正加锁之前不需要遍历表中的⾏是否加锁,只需要查看⼀下表中的意向锁即可;
• 在请求锁的过程中,如果将要请求的锁与现有锁兼容,则将锁授予请求的事务,如果与现有锁冲
突,则不会授予;事务将阻塞等待,直到冲突的锁被释放;意向锁与⾏级锁的兼容性如下表:

• 除了全表锁定请求之外,意向锁不会阻⽌任何锁请求;意向锁的主要⽬的是表⽰事务正在锁定某⾏或者正在意图锁定某⾏。

3.索引记录锁 - Record Locks

• 索引记录锁或称为精准⾏锁,顾名思意是指索引记录上的锁,如下SQL锁住的是指定的⼀⾏:

sql 复制代码
# 防⽌任何其他事务插⼊、更新或删除值为1的⾏,id为索引列
SELECT * FROM account WHERE id = 1 For UPDATE;

• 索引记录锁总是锁定索引⾏,在表没有定义索引的情况下,InnoDB创建⼀个隐藏的聚集索引,并
使⽤该索引进⾏记录锁定,当使⽤索引进⾏查找时,锁定的只是满⾜条件的⾏,如图所⽰

4.间隙锁 - Gap Locks

• 间隙锁锁定的是索引记录之间的间隙,或者第⼀个索引记录之前,再或者最后⼀个索引记录之后的间隙。如图所⽰位置,根据不同的查询条件都可能会加间隙锁


• 例如有如下SQL,锁定的是ID (10, 20)之间的间隙,注意不包括10和20的⾏,⽬的是防⽌其他事务将ID值为15的列插⼊到列 account 表中(⽆论是否已经存在要插⼊的数据列),因为指定范围值之
间的间隙被锁定了;

sql 复制代码
SELECT * FROM account WHERE id BETWEEN 10 and 20 For UPDATE;

间隙可以跨越单个或多个索引值;


• 对于使⽤唯⼀索引查询到的唯⼀⾏,不使⽤间隙锁,如下语句,id列有唯⼀的索引,只对id值为
100的⾏使⽤索引记录锁:

sql 复制代码
# 只使⽤Record Locks
SELECT * FROM account WHERE id = 100;

• 如果id没有被索引,或者是⼀个⾮唯⼀的索引,以上语句将锁定对应记录前⾯的间隙;
• 不同事务的间隙锁可以共存,⼀个事务的间隙锁不会阻⽌另⼀个事务在相同的间隙上使⽤间隙锁;共享间隙锁和独占间隙锁之间没有区别。
• 当事务隔离级别设置为 READ COMMITTED 时间隙锁会被禁⽤,对于搜索和索引扫描不再使⽤间隙锁定。

5.临键锁 - Next-Key Locks

• Next-key 锁是索引记录锁和索引记录之前间隙上间隙锁的组合,如图所⽰;


• InnoDB搜索或扫描⼀个表的索引时,执⾏⾏级锁策略,具体⽅式是:在扫描过程中遇到的索引记
录上设置共享锁或排他锁,因此,⾏级锁策略实际上应⽤的是索引记录锁。索引记录上的 next
key 锁也会影响该索引记录之前的"间隙",也就是说, next-key 锁是索引记录锁加上索引记录
前⾯的间隙锁。如果⼀个会话对索引中的⼀条记录R具有共享锁或排他锁,则另⼀个会话不能在索
引记录R之前的空⽩中插⼊新的索引记录⾏。
• 默认情况下, REPEATABLE READ 事务隔离级别开启 next-key 锁并进⾏搜索和索引扫描,可
以防⽌幻象⾏,从⽽解决幻读问题

6.插入意向锁 - Insert Intention Locks

• 插⼊意向锁是⼀个特殊的间隙锁,在向索引记录之前的间隙进⾏insert操作插⼊数据时使⽤,如果
多个事务向相同索引间隙中不同位置插⼊记录,则不需要彼此等待。假设已经存在值为10和20的索引记录,两个事务分别尝试插⼊索引值为15和16的⾏,在获得插⼊⾏上的排他锁之前,每个事务都⽤插⼊意向锁锁住10到20之间的间隙,但不会相互阻塞,因为他们所操作的⾏并不冲突;
• 下⾯的⽰例演⽰⼀个事务在获得插⼊记录的排他锁之前,使⽤了插⼊意向锁:(WPS)

AUTO-INC Locks(了解)

死锁:(更详细的可以在linux系统博客中了解,这里不多介绍)

• 由于每个事务都持有另⼀个事务所需的锁,导致事务⽆法继续进⾏的情况称为死锁。以下图为例,两个事务都不会主动释放⾃⼰持有的锁,并且都在等待对⽅持有的资源变得可⽤。


死锁产⽣的条件
• 互斥访问:如果线程1获取到了锁A,那么线程2就不能同时得到锁A
• 不可抢占:获取到锁的线程,只能⾃⼰主动释放锁,别的线程不能从他的⼿中抢占锁
• 保持与请求:线程1已经获得了锁A,还要在这个基础上再去获了锁B
• 循环等待:线程1等待线程2释放锁,线程2也等待线程1释放锁,死锁发⽣时系统中⼀定有由两个或两个以上的线程组成的⼀条环路,该环路中的每个线程都在等待着下⼀个进程释放锁
以上四条是造成死锁的必要条件,必须同时满⾜,所以如果想要打破死锁,可以破坏以上四个条件之⼀,最常⻅的⽅式就是打破循环等待

InnoDB对死锁的检测
• InnoDB在运⾏时会对死锁进⾏检测,当死锁检测启⽤时(默认),InnoDB⾃动检测事务死锁,并回
滚⼀个或多个事务来打破死锁。InnoDB尝试选择⼩事务进⾏回滚,其中事务的⼤⼩由插⼊、更新
或删除的⾏数决定。
• 如果系统变量 innodb_table_locks = 1 (默认) 和 autocommit = 0 ,InnoDB可以检测
到表级锁和⾏级锁级别发⽣的死锁;否则,⽆法检测到由 lock TABLES 语句设置的表锁或由⾮
InnoDB存储引擎设置的锁,对于⽆法检测到的死锁,可以通过设置系统变量
innodb_lock_wait_timeout 的值来指定锁的超时时间来解决死锁问题
• 当超过 200 个事务等待锁资源或等待的锁个数超过 1,000,000 个时也会被视为死锁,并尝试将
等待列表的事务回滚。

• 在⾼并发系统中,多个线程等待相同的锁时,死锁检测可能会导致性能降性变慢,此时禁⽤死锁检测并依赖 innodb_lock_wait_timeout 设置进⾏事务回滚可能性能更⾼。可以通过设置系统
变量 innodb_deadlock_detect[={OFF|ON}] 禁⽤死锁检测。
如何避免死锁
• MySQL是⼀个多线程程序,死锁的情况⼤概率会发⽣,但他并不可怕,除⾮频繁出现,导致⽆法运⾏某些事务
• InnoDB使⽤⾃动⾏级锁,即使在只插⼊或删除单⾏的事务中,也可能出现死锁。这是因为插⼊或
删除⾏并不是真正的"原⼦"操作,同时会对索引记录进⾏修改并设置锁
• 使⽤以下技术来处理死锁并降低发⽣死锁的可能性:
◦ 使⽤事务⽽不是使⽤ LOCK TABLES 语句⼿动加锁,并使⽤
innodb_lock_wait_timeout 变量设置锁的超时时间,保证任何情况下锁都可以⾃动释放
◦ 经常使⽤ SHOW ENGINE INNODB STATUS 命令来确定最近⼀次死锁的原因。这可以帮助我
们修改应⽤程序以避免死锁
◦ 如果出现频繁的死锁警告,可以通过启⽤ innodb_print_all_deadlocks 变量来收集调
试信息。对于死锁的信息,都记录在MySQL错误⽇志中,调试完成后记得禁⽤此选项
◦ 如果事务由于死锁⽽失败,记得重新发起事务,再执⾏⼀次
◦ 尽量避免⼤事务,保持事务粒度⼩且持续时间短,这样事务之间就不容易发⽣冲突,从⽽降低
发⽣死锁的概率
◦ 修改完成后⽴即提交事务也可以降低死锁发⽣的概率。特别注意的是,不要在⼀个交互式会话
中⻓时间打开⼀个未提交的事务
◦ 当事务中要修改多个表或同⼀表中的不同⾏时,每次都要以⼀致的顺序执⾏这些操作,使事务
中的修改操作形成定义良好的队列,可以避免死锁。⽽不是在不同的位置编写多个类似的
INSERT、UPDATE和DELETE语句。我们写的程序其实就是把⼀系列操作组织成⼀个⽅法或函数
◦ 向表中添加适当的索引,以便查询时扫描更少的索引并设置更少的锁,可以使⽤EXPLAIN
SELECT来确定哪些索引⽤于当前的查询
◦ 使⽤表级锁防⽌对表进⾏并发更新,可以避免死锁,但代价是系统的并发性降低
◦ 如果在查询时加锁,⽐如 SELECT...FOR UPDATE 或 SELECT...FOR SHARE ,尝试使⽤较低
的隔离级别,⽐如 READ COMMITTED

6.事务的隔离级别

• 事务的隔离级别分为全局作⽤域和会话作⽤域,查看不同作⽤域事务的隔离级别,可以使⽤以下的⽅式:

sql 复制代码
# 全局作⽤域
SELECT @@GLOBAL.transaction_isolation;
# 会话作⽤域
SELECT @@SESSION.transaction_isolation;
# 可以看到默认的事务隔离级别是REPEATABLE-READ(可重复读)
+---------------------------------+
| @@SESSION.transaction_isolation |
+---------------------------------+
| REPEATABLE-READ | # 默认是可重复读
+---------------------------------+
1 row in set (0.00 sec)

• 设置事务的隔离级别和访问模式,可以使⽤以下语法:

sql 复制代码
# 通过GLOBAL|SESSION分别指定不同作⽤域的事务隔离级别
SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL level|access_mode;
# 隔离级别
level: {
 REPEATABLE READ # 可重复读
 | READ COMMITTED # 读已提交
 | READ UNCOMMITTED # 读未提交
 | SERIALIZABLE # 串⾏化
}
# 访问模式
access_mode: {
 READ WRITE # 表⽰事务可以对数据进⾏读写
 | READ ONLY # 表⽰事务是只读,不能对数据进⾏读写
}
# ⽰例
# 设置全局事务隔离级别为串⾏化
SET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE;
# 设置会话事务隔离级别为串⾏化
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
# 如果不指定任何作⽤域,设置将在下⼀个事务开始⽣效
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

通过选项⽂件指定事务的隔离级别,以便MySQL启动的时候读取并设置

sql 复制代码
[mysqld]
transaction-isolation = REPEATABLE-READ # 隔离级别为可重复读
transaction-read-only = OFF # 关闭只读意味着访问模式为读写

• 通过SET语法设置系统变量的⽅式设置事务的隔离级别

sql 复制代码
# ⽅式⼀
SET GLOBAL transaction_isolation = 'SERIALIZABLE';
# 注意使⽤SET语法时有空格要⽤"-"代替
SET SESSION transaction_isolation = 'REPEATABLE-READ'; 
# ⽅式⼆
SET @@GLOBAL.transaction_isolation='SERIALIZABLE';
# 注意使⽤SET语法时有空格要⽤"-"代替
SET @@SESSION.transaction_isolation='REPEATABLE-READ';

• 设置事务隔离级别的语句不能在已开启的事务中执⾏,否则将会报错:

sql 复制代码
# 开启事务
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
# 修改事务的隔离级别,将会报错
mysql> SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
ERROR 1568 (25001): Transaction characteristics can't be changed while a 
transaction is in progress

1.read uncommitted - 读未提交与脏读

一、核心概念定义

1. 读未提交(Read Uncommitted)

它是数据库事务的最低隔离级别,允许一个事务读取另一个事务 "尚未提交" 的数据。简单说:事务 A 还没把数据改完、没点 "提交",事务 B 就能看到 A 修改后的临时数据。

2. 脏读(Dirty Read)

它是在 "读未提交" 级别下产生的数据一致性问题,指一个事务读取到了另一个事务 "修改后但未提交" 的数据,而后续该事务因异常回滚,导致之前读取的数据变成 "无效脏数据"。简单说:事务 B 读了事务 A 没提交的临时数据,结果 A 后悔了把修改撤了(回滚),事务 B 手里的数据就成了 "假数据"。

二、用实例理解:从场景看二者关系

以 "银行转账" 为例,假设用户甲账户有 1000 元,现在要给用户乙转 500 元,用两个事务模拟过程:

步骤 事务 A(甲转账给乙) 事务 B(查询甲的账户余额) 结果说明
1 开始事务,执行 SQL:UPDATE 账户 SET 余额=500 WHERE 用户名=甲 - 事务 A 修改了甲的余额为 500,但未提交
2 - 开始事务,执行 SQL:SELECT 余额 FROM 账户 WHERE 用户名=甲 因隔离级别是 "读未提交",事务 B 读到 "500 元"(这就是脏读)
3 发现转账信息错误,执行 "事务回滚" - 事务 A 的修改被撤销,甲的余额恢复为 1000 元
4 - 事务 B 基于读到的 "500 元" 做后续操作(如统计、展示) 事务 B 手里的 "500 元" 是无效的脏数据,导致业务结果错误

三、关键区别与联系

维度 读未提交(Read Uncommitted) 脏读(Dirty Read)
本质 事务隔离级别的一种(最低级别) 数据一致性问题(错误现象)
逻辑关系 是 "因",是产生脏读的前提 是 "果",是该级别下的必然结果
对数据影响 定义了事务间数据可见性规则 导致读取到无效数据,破坏一致性
是否可避免 可通过提升隔离级别规避(如升到读已提交) 提升隔离级别后,该问题会消失

四、实际应用场景

  • 读未提交的适用场景:仅用于对数据一致性要求极低、追求极致查询性能的场景(如实时监控系统看瞬时数据,即使数据错了也不影响核心业务),绝大多数业务(如金融、电商、支付)都不会用。
  • 脏读的危害:会直接导致业务逻辑错误,比如统计报表算错金额、订单金额显示异常、库存计算偏差等,因此生产环境中几乎都会通过提升隔离级别(如读已提交、可重复读)来避免。

总结

简单记:读未提交是 "规则",脏读是 "规则下的坑"------ 按 "读未提交" 的规则办事,就一定会掉进 "脏读" 的坑里。要解决脏读,核心是把事务隔离级别提升到 "读已提交"(Read Committed)及以上,此时事务只能读取其他事务 "已提交" 的数据,从根本上杜绝脏读。

2.read committed - 读已提交与不可重复读

一、核心概念定义

1. 读已提交(Read Committed)

它是数据库中常用的基础隔离级别(如 MySQL 默认隔离级别),要求一个事务只能读取另一个事务 "已经提交" 的数据,能直接避免 "脏读" 问题。简单说:事务 A 必须把数据修改完并 "提交",事务 B 才能看到修改后的结果;没提交的临时数据,事务 B 看不到。

2. 不可重复读(Non-repeatable Read)

它是在 "读已提交" 级别下产生的数据一致性问题,指同一个事务内,对同一条数据进行多次读取,结果却不一致 ------ 因为在两次读取之间,另一个事务修改了该数据并提交,导致后一次读取到了新的提交结果。简单说:事务 B 第一次读某条数据是 "100",中间事务 A 改了数据并提交成 "200",事务 B 再读同一条数据就变成了 "200",同个事务内 "重复读" 的结果不一样。

二、用实例理解:从场景看二者关系

以 "电商商品库存查询" 为例,假设某商品初始库存为 10 件,用两个事务模拟用户查询库存和商家修改库存的过程:

步骤 事务 B(用户查询商品库存) 事务 A(商家修改商品库存) 结果说明
1 开始事务,第一次查询库存:SELECT 库存 FROM 商品 WHERE 商品ID=1 - 事务 B 读到库存为 "10 件"(此时无其他事务修改)
2 - 开始事务,修改库存:UPDATE 商品 SET 库存=8 WHERE 商品ID=1,并执行 "提交" 事务 A 修改完成并提交,数据正式更新
3 同一个事务内,第二次查询库存:SELECT 库存 FROM 商品 WHERE 商品ID=1 - 因隔离级别是 "读已提交",事务 B 读到新的提交结果 "8 件",与第一次读取的 "10 件" 不一致,即发生 "不可重复读"

三、关键区别与联系

维度 读已提交(Read Committed) 不可重复读(Non-repeatable Read)
本质 事务隔离级别(中低级别) 数据一致性问题(读一致性破坏)
逻辑关系 是 "前提",解决了脏读,但允许不可重复读 是 "衍生问题",是该级别下无法避免的现象
核心差异点 只允许读取 "已提交" 数据,杜绝临时数据 同事务内多次读同一数据,结果随其他事务提交而变化
与脏读的区别 能完全避免脏读(因为不读未提交数据) 脏读是读 "无效临时数据",不可重复读是读 "有效但变化的数据"

四、实际应用与解决思路

1. 读已提交的适用场景

  • 适用于对 "读一致性" 要求不高,但需要避免脏读的场景,如:
    • 电商商品详情页(用户每次刷新看到最新库存即可,无需同一次浏览中保持库存不变);
    • 新闻资讯类应用(读取文章内容,允许其他事务修改并实时呈现更新)。
  • 优点是性能较好,兼顾了一致性和并发效率,是多数业务的默认选择。

2. 如何解决不可重复读

若业务需要 "同事务内多次读同一数据结果一致"(如财务对账、订单金额计算),需提升隔离级别或使用锁机制:

  • 提升隔离级别:将隔离级别升到 "可重复读"(Repeatable Read,如 MySQL 默认隔离级别其实是可重复读,会通过 MVCC 机制避免不可重复读)或 "串行化"(Serializable);
  • 使用行锁 / 表锁:在查询时加共享锁(如 SELECT ... FOR SHARE),阻止其他事务修改当前数据,直到本事务结束。

总结

读已提交的核心价值是 "杜绝脏读",同时保持较好的并发性能,但代价是 "无法保证同事务内读一致性",从而产生不可重复读。是否需要解决不可重复读,取决于业务场景 ------ 若同事务内多次读取需结果稳定(如财务计算),则需升级隔离级别;若允许读取最新提交数据(如商品浏览),则读已提交完全够用。

3.repeatable read - 可重复读与幻读

一、核心概念定义

1. 可重复读(Repeatable Read)

它是多数数据库(如 MySQL InnoDB)的默认隔离级别,核心能力是 "保证同一事务内,多次读取同一批数据的结果始终一致",即使其他事务修改或删除了该批数据并提交,本事务也看不到变化。简单说:事务 B 第一次读了 "库存> 5 的商品",之后事务 A 修改了其中某商品的库存或删除了一条记录并提交,事务 B 再读 "库存 > 5 的商品",结果和第一次完全一样。

2. 幻读(Phantom Read)

它是在 "可重复读" 级别下可能出现的特殊数据一致性问题,指同一事务内,多次执行 "相同条件的范围查询" 时,因其他事务 "插入或删除了符合条件的新数据" 并提交,导致前后查询结果的 "数据行数不一致",像出现了 "幻觉" 一样。简单说:事务 B 第一次查 "库存> 5 的商品" 有 3 条,事务 A 插入了 1 条新的库存 > 5 的商品并提交,事务 B 再查同一条件,结果变成了 4 条 ------ 新增的 1 条数据就是 "幻行",导致行数不一致。

二、用实例理解:从场景看二者关系

以 "电商统计库存> 5 的商品数量" 为例,用两个事务模拟过程:

步骤 事务 B(统计库存 > 5 的商品) 事务 A(新增库存 > 5 的商品) 结果说明
1 开始事务,第一次范围查询:SELECT COUNT(*) FROM 商品 WHERE 库存>5 - 事务 B 查到结果为 "3 条"(此时无其他事务插入数据)
2 - 开始事务,插入新商品:INSERT INTO 商品(商品名, 库存) VALUES('新品', 8),并执行 "提交" 事务 A 新增了 1 条符合 "库存> 5" 的商品,数据正式写入
3 同一个事务内,第二次范围查询:SELECT COUNT(*) FROM 商品 WHERE 库存>5 - 若数据库未特殊处理幻读,事务 B 会读到 "4 条",与第一次的 "3 条" 行数不一致,即发生 "幻读"
4 事务 B 尝试修改所有库存 > 5 的商品:UPDATE 商品 SET 折扣=0.9 WHERE 库存>5 - 此时会修改到 4 条商品(包括事务 A 新增的 1 条),但事务 B 之前只查到 3 条,出现 "修改行数与查询行数不匹配" 的矛盾

三、关键区别与联系

维度 可重复读(Repeatable Read) 幻读(Phantom Read)
本质 事务隔离级别(中高级别) 数据一致性问题(范围查询行数不一致)
逻辑关系 是 "进阶隔离能力",解决了不可重复读,但对 "新增 / 删除数据" 的范围查询保护不足 是 "残留问题",仅在 "范围查询 + 其他事务插入 / 删除" 场景下出现
与不可重复读的差异 针对 "已有数据的修改 / 删除",保证同事务内读取结果一致 针对 "新插入 / 删除的符合条件的数据",导致范围查询行数变化
典型场景 单条数据多次读取(如订单金额反复校验) 范围数据统计 / 修改(如统计库存 > 5 的商品、批量更新)

四、实际应用与幻读的处理

1. 可重复读的适用场景

  • 适用于对 "同事务内数据一致性" 要求较高的场景,如:
    • 财务对账(多次计算同一账户的收支总和,结果必须一致);
    • 订单创建流程(反复校验商品库存、价格,避免中途被修改导致订单异常)。
  • 优点是在保证高一致性的同时,并发性能优于最高级别 "串行化",是多数业务的首选。

2. 幻读的解决思路

不同数据库对幻读的处理方式不同(如 MySQL InnoDB 通过特殊机制减少幻读,PostgreSQL 则需更高级别),常见解决方案有两种:

  1. 提升隔离级别到 "串行化"(Serializable):最高隔离级别,会对范围查询加 "范围锁",阻止其他事务插入 / 删除符合条件的数据,从根本上杜绝幻读,但会降低并发效率。
  2. 使用 "Next-Key Lock"(InnoDB 特有):MySQL InnoDB 在可重复读级别下,默认使用 Next-Key Lock(行锁 + 间隙锁),会锁定查询范围的 "数据行 + 间隙",阻止其他事务在间隙中插入新数据,从而有效避免幻读(这也是 MySQL 可重复读级别实际很少出现幻读的原因)。

总结

可重复读的核心价值是 "解决不可重复读,保证同事务内数据一致性",同时兼顾并发性能,而幻读是其在 "范围查询 + 插入 / 删除" 场景下的特殊残留问题。多数情况下,MySQL InnoDB 的 Next-Key Lock 已能规避幻读,无需升级到串行化;只有对范围查询一致性要求极高的场景(如银行批量对账),才需要用串行化彻底杜绝。

4.serializable - 串行化

实现方式:
1.读取时 :加共享表锁,读取版本链中的最新版本,事务结束时释放;
2.更新时 :加独占表锁,事务结束时释放,完全串⾏操作,可以解决所有事务问题
所有的更新都是串⾏操作,效率极低

5.多版本控制(MVCC)

一、MVCC 核心定义:不止一个 "数据版本"

MVCC 全称 Multi-Version Concurrency Control,即多版本并发控制 。它的核心逻辑是:InnoDB 不会直接覆盖数据的旧值,而是为每一行数据保存多个 "历史版本",每个事务根据自身的 "版本视角",读取对应版本的数据,从而实现 "读不阻塞写、写不阻塞读"。

简单说:事务 A 修改数据时,会生成数据的 "新版本",同时保留 "旧版本";事务 B 此时读取该数据,会拿到 "旧版本",不会被事务 A 的修改阻塞,双方互不干扰。

二、MVCC 实现的 3 个关键技术

InnoDB 通过 3 个核心组件协同实现 MVCC,缺一不可:

1. 隐藏列:给每行数据 "打版本标签"

InnoDB 会为表中的每一行数据,自动添加 3 个隐藏列(无需手动定义),用于记录版本信息:

  • DB_TRX_ID:最近一次修改该数据的 "事务 ID"(唯一标识一个事务)。
  • DB_ROLL_PTR:"回滚指针",指向该数据的 "上一个历史版本"(类似链表,串联所有版本)。
  • DB_ROW_ID:若表没有主键或唯一索引,InnoDB 会自动生成该列作为行唯一标识。

2. undo log:保存数据的 "历史版本"

undo log(回滚日志)是存储数据历史版本的 "仓库"。当事务修改数据时:

  • 先将数据的 "旧版本" 写入 undo log,通过 DB_ROLL_PTR 与当前版本关联;
  • 若事务需要回滚,或其他事务需要读旧版本,就从 undo log 中获取历史数据;
  • undo log 会在 "没有事务需要用到该版本" 时,被 InnoDB 自动清理,避免占用空间。

3. Read View:事务的 "版本过滤规则"

Read View(读视图)是事务启动时生成的 "版本判断依据",它决定了当前事务 "能看到哪些版本的数据",核心包含 4 个参数:

  • m_ids:当前 "活跃的事务 ID 列表"(即还没提交的事务)。
  • min_trx_id:活跃事务中的 "最小事务 ID"。
  • max_trx_id:InnoDB 下一个要分配的 "事务 ID"(比当前所有事务 ID 都大)。
  • creator_trx_id:当前事务自身的 "事务 ID"。

事务读取数据时,会用 Read View 过滤数据的 DB_TRX_ID(修改该数据的事务 ID),规则如下:

  1. 若 DB_TRX_ID < min_trx_id:修改该数据的事务已提交,当前事务可读取该版本;
  2. 若 DB_TRX_ID > max_trx_id:修改该数据的事务是 "未来事务"(当前事务启动后才开始),不可读取;
  3. 若 min_trx_id ≤ DB_TRX_ID ≤ max_trx_id:看 DB_TRX_ID 是否在 m_ids 中 ------ 在则事务未提交,不可读;不在则已提交,可读。


• 这样从版本链头遍历判断到版本链尾,找到⾸个符合要求的版本即可,就可以实现查询到的结果都是已经提交事务的数据,解决了脏读问题。

版本链:
• MVCC的实现是基于 Undo Log 版本链和 ReadView 来完成的,Undo Log做为回滚的基础,在
执⾏Update或Delete操作时,会将每次操作的上⼀个版本记录在Undo Log中,每条Undo Log中都
记录⼀个叫做 roll_pointer 的引⽤信息,通过 roll_pointer 就可以将某条数据对应的
Undo Log组织成⼀个Undo链,在数据⾏的头部通过数据⾏中的 roll_pointer 与Undo Log中
的第⼀条⽇志进⾏关联,这样就构成⼀条完整的数据版本链,如下图所⽰:

• 每⼀条被修改的记录都会有⼀条版本链,体现了这条记录的所有变更,当有事务对这条数据进⾏修改时,将修改后的数据链接到版本链接的头部,如下图中 UNDO3

三、用实例理解:MVCC 如何实现 "读不阻塞写"

以 "用户查余额" 和 "系统扣余额" 两个事务为例,假设用户初始余额 1000 元,事务 ID 从 100 开始分配:

步骤 事务 A(系统扣余额,ID=100) 事务 B(用户查余额,ID=101) MVCC 底层操作
1 开始事务,执行 UPDATE 账户 SET 余额=900 WHERE 用户ID=1 - 1. 把余额 1000 的旧版本写入 undo log;2. 更新当前行:DB_TRX_ID=100,余额 = 900,DB_ROLL_PTR 指向 undo log 的旧版本。
2 - 开始事务,生成 Read View:m_ids=[100],min_trx_id=100,max_trx_id=102,creator_trx_id=101 事务 B 的读视图确定:只看 "修改事务已提交" 的版本。
3 - 执行 SELECT 余额 FROM 账户 WHERE 用户ID=1 1. 读取当前行:DB_TRX_ID=100,在 m_ids 中(事务 A 未提交),不可读;2. 通过 DB_ROLL_PTR 找到 undo log 中的旧版本(余额 1000,DB_TRX_ID<100,已提交);3. 返回余额 1000 给事务 B。
4 提交事务 - 事务 A 提交,m_ids 中移除 100;但事务 B 的 Read View 是启动时生成的,不会更新。
5 - 再次执行相同查询 事务 B 仍用初始 Read View 判断,还是读取 undo log 中的旧版本(1000),实现 "可重复读"。

四、MVCC 与事务隔离级别的关系

MVCC 不是独立存在的,它会根据隔离级别的不同,通过 "Read View 生成时机" 的差异,实现不同的隔离效果:

  • 读未提交(Read Uncommitted):不生成 Read View,直接读当前行最新数据,所以能看到未提交的修改;
  • 读已提交(Read Committed):每次执行查询时,都会重新生成 Read View,所以能看到 "查询时已提交的最新版本"(会出现不可重复读);
  • 可重复读(Repeatable Read):只在事务 "第一次执行查询" 时生成 Read View,后续查询复用该视图,所以同事务内多次读结果一致(解决不可重复读);
  • 串行化(Serializable):不依赖 MVCC,直接用锁(读加共享锁,写加排他锁)强制事务串行执行,杜绝所有数据问题。

总结

MVCC 是 InnoDB 实现 "高并发 + 事务隔离" 的核心,它通过 "隐藏列记版本、undo log 存历史、Read View 做过滤" 的组合,让读写操作并行执行,既避免了锁竞争导致的性能损耗,又满足了多数业务的隔离需求。尤其是在可重复读级别下,MVCC 完美解决了不可重复读问题,成为 MySQL 高性能的重要原因之一。
MVCC是否可以解决不可重复读与幻读

• ⾸先幻读⽆法通过MVCC单独解决

• 对于不可重复读问题,在事务中的第⼀个查询时创建⼀个ReadView,后续查询都是⽤这个 ReadView进⾏判断,所以每次的查询结果都是⼀样的,从⽽解决不可重复读问题,在 REPEATABLE READ 可重复读,隔离级别下就采⽤的这种⽅式

• 如果事务每次查询都创建⼀个新的ReadView,这样就会出现不可重复读问题,在 READ COMMITTED 读已提交的隔离级别下就是这种实现⽅式

面试题:

什么是事务?
有哪些隔离级别,分别解决了什么问题?
谈⼀下MVCC
MySQL 锁的分类
描述⾏级锁、表级锁特点

相关推荐
zzzsde3 小时前
【C++】stack和queue:使用&&OJ题&&模拟实现
开发语言·c++
又见野草3 小时前
软件设计师知识点总结:结构化开发
数据库·webview·结构化开发
软件2054 小时前
【JDK、JRE、JVM】
java·开发语言·jvm
Wind哥4 小时前
VS Code搭建C/C++开发调试环境-Windows
c语言·开发语言·c++·visual studio code
csbysj20204 小时前
CSS 属性选择器详解
开发语言
彭同学她同桌4 小时前
Mac-终端
开发语言·javascript·macos
半路_出家ren5 小时前
MySQL数据库,DDL,DML,查询,权限,主从复制
数据库·mysql·主从复制·权限·ddl·dml
运维成长记5 小时前
Mysql的数据备份和高可用
数据库·mysql
IT技术与企业应用结合的爱好者5 小时前
c#using Oracle.ManagedDataAccess.Client 批量保存数据
数据库·oracle