首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >MySQL 事务

MySQL 事务

作者头像
编程之心
发布2022-05-10 09:43:01
2.8K0
发布2022-05-10 09:43:01
举报
文章被收录于专栏:编程之禅编程之禅

数据库的事务

事务的定义

  • 事务是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列组成;因为它是数据库最小的工作单元,是不可再分的;它还可能包含了一个或者一系列的 **DML** 语句(**insert、update、delete**),现在 **MySQL** 中有 **InnoDB & NDB** 存储引擎支持事务。

事务的四大特性

  • 原子性(**Atomicity**):在 **InnoDB** 存储引擎中通过 **undo log** 来实现了,它记录了数据修改之前的值(逻辑日志),当发生异常时就可以使用 **undo log** 来实现回滚操作。所以要嘛成功,否则失败;
  • 一致性(**Consistency**):指数据库的完整性约束没有被破坏,事务执行的前后都是合法的数据状态;除了数据库自身的完整性约束外,还有用户自定义的完整性,该方式通常在代码中控制;
  • 隔离性(**Isolation**):在有了事务的定义后,在数据库里会有很多的事务同时会去操作同一张表或同一行数据,这时会产生并发或干扰的操作;那么对于隔离性的定义就是多个事务对表或行进行并发操作时,应该是透明且互不干扰的,通过这种方式来最终也是保证业务数据的一致性;
  • 持久性(**Durability**):在对数据库的任何操作,只要事务提交成功,数据就是永久性的,不能因为系统宕机或重启数据库的服务器又恢复到原来的状态。持久性是通过 **redo log & double write** 双写缓冲来实现的。在操作数据库时,会先写到内存的 **buffer pool** 里并且记录 **redo log**;如果在刷盘之前出现了异常,在重启后就可以读取 **redo log** 的内容,写入到磁盘,保证数据的持久性。当然,恢复成功的前提是数据页本身是没有被破坏的,这个是通过 **double write** 来保证。
  • 原子性、隔离性以及持久性最终都是为了实现一致性。

数据库出现事务的场景

  • 当使用 **Spring** 框架的事务或类似 **Navicat** 客户端工具操作数据库,最终都是发送一个指令到数据库中执行。在 **InnoDB** 存储引擎中的事务默认情况下是开启自动提交的,所以在下面的 **update SQL** 语句是自动开启了一个事务并提交,最终写入到了磁盘;当设置 **autocommit = false** 时,数据库的事务需要手动地区开启与结束。手动开启事务有 **begin | start transaction** 方式,手动结束事务有 **commit | rollback**** **方式;在回滚时和当客户端的连接断开时,事务也会结束。
-- 数据库版本 8.0.11
select version(); 
-- 默认引擎 InnoDB
show variables like '%engine%'; 
-- 查看是否自动提交,默认是
show variables like 'autocommit';
-- 做更新操作
update employees set gender = 'M' where emp_no = 1;

事务在并发时带来的问题

  • 当很多事务并发地区操作数据库的表或行时,如果没有事务的隔离性脏读、不可重复读和幻读问题。
  • 脏读:存在 **TransactionA & TransactionB**** **两个事务情况下:
    • 首先 **TransactionA** 里首先通过 **where id = 1** 的条件去查询一条数据,返回 **name = John,age = 23** 的数据;
    • 然后 **TransactionB** 同样去操作 **where id = 1** 的这行数据,通过一个 **update** 的操作把这行数据的 **age** 修改为了 **18**,但事务还没提交;
    • 这时 **TransactionA** 再次去执行相同的查询操作,发现数据发送了变化,获取到 **age = 18** 的数据;那么在一个事务里,由于另一个事务在一个时间段内修改了数据并且没有提交而导致前后两次读取的数据不一致的情况就是事务并发里的脏读问题
image.png
image.png
  • 不可重复读:存在 **TransactionA & TransactionB**** **两个事务情况下:
    • **TransactionA** 通过 **where id = 1** 查询到一条数据,然后在 **TransactionB** 里执行了一个 **update** 操作并提交修改的事务;接着在 **TransactionA** 里读取到了其他事务已经提交的数据而导致前后两次读取数据不一致的情况,这种一个事物读取到了其他事务已提交的数据导致前后两次读取数据不一致的情况在事务并发里称之为不可重复读问题。
image.png
image.png
  • 幻读:存在 **TransactionA & TransactionB**** **两个事务情况下:
    • **TransactionA** 中执行了一个范围查询,这时满足条件的数据只有一条;在 **TransactionB** 里插入了一行数据并提交该事务;然后在 **TransactionA** 中再次去查询时就发现多了一行数据;这种一个事务前后两次读取数据不一致是由其他事务插入数据造成的的情况称之为事务中的幻读问题。
image.png
image.png
  • 对于脏读、不可重复读以及幻读都是数据库的读一致性问题,都是在一个事务中前后两次读取出现了不一致的情况。该问题需要由数据库提供一定的事务隔离机制来解决。

SQL92 标准

  • 有很多的数据库厂商按照 **SQL92** 标准提供一定的事务隔离级别来解决事务并发的问题。但是不同的数据库厂商或存储引擎实现有部分的差异,比如 **Oracle** 里只有两种 **ReadCommited** 已提交读和 **Seriallizable**
    • **Read Uncommitted** 未提交读:表示当一个事务可以读取到其他事务未提交的数据时,就会出现脏读,它没有解决任何问题;
    • **Read Committed** 已提交读:表示一个事务只能读取到其他事务已提交的数据,不能读取到其他事务未提交的事务,它解决了脏读的问题;
    • **Repeatable Read** 可重复读:表示在同一个事务里多次读取同样数据的结果是一致的,它解决了不可重复读的问题;
    • **Serializable** 串行化:在这个隔离级别中的所有事务都是串行执行的,对数据的操作需要排队,已经不存在事务的并发操作,它解决了所有的问题。

MySQL InnoDB 中对隔离级别的支持

  • **MySQL** 中的 **InnoDB** 存储引擎支持的隔离级别与 **SQL92** 定义的基本一致,隔离级别越高,事务的并发度就越低。在 **InnoDB** 存储引擎中的 **Repeatable Read** 解决了幻读的问题,这个也是 **InnoDB** 默认使用 **RR** 作为事务隔离级别的原因,既能保证数据的一致性,又支持比较高的并发度。
  • **Read Uncommitted****:**在 **RU** 隔离级别下不加锁;
  • **Read Committed**:在 **RC** 隔离级别下,普通的 **select** 都是快照读,使用 **MVCC** 实现,加锁的 **select** 都是使用记录锁,因为没有 **Gap Lock**;另外外键约束检查(foreign-key constraint checking)和重复键检查(**duplicate-key checking**)时会使用间隙锁来封锁区间,所以 **RC** 会出现幻读的原因。
  • **Repeatable Read**:在 **RR** 隔离级别下,普通的 **select** 使用快照读,底层使用 **MVVC** 来实现。但加锁的 **select(select .... in share mode / select ... for update)** 和更新操作 **delete/update** 等语句使用当前读,底层使用记录锁间隙锁临键锁实现。
  • **Serializable**:在 **Serializable** 隔离级别下的所有 select 语句都会被隐式地转换为 **select ... in share mode**,会和 **update/delete** 语句互斥。

事务隔离级别

脏读

不可重复读

幻读

Read Uncommitted 未提交读

×

×

×

Read Committed 已提交读

×

×

Repeatable Read 可重复读

√ InnoDB

Serializable 串行化

  • 事务级别的选择**RU & Serializable** 不能选用,因为它们要嘛不能解决事务的并发问题,要嘛不能性能太低。**RC & RR** 之间的区别:
    • **RR** 的间隙锁会导致锁定范围的扩大;
    • 条件列未使用到索引,**RR** 会锁表,**RC** 会锁行;
    • **RC** 的半一致性(**semi-consistent**)读可以增加 **update** 操作的并发性;另外在 **RC** 中的一个 **update** 操作在读到一行已经加锁的记录,此时的 **InnoDB** 返回记录最近提交的版本,由 **MySQL** 的上层判断此版本是否满足 **update****where** 条件。如果满足的情况下,**MySQL** 会重新发起一次读操作,此时会读取行的最新版本并加锁。

解决读一致性问题的方式

  • 一般情况下保证事务前后两次读取数据的结果一致的事务隔离实现方式为:
    • LBCC:在需要保证前后两次读的数据一致情况下可以在读取数据时,锁定需要操作的数据,不让其他事务有机会对其进行修改;这种方式叫基于锁的并发控制 **Lock Based Concurrency Control**。如果只是基于锁来实现事务的隔离,一个事务读取时不允许其他事务进行修改就意味着不支持并发的读写操作。而大多数的应用场景都是读多邪少的,这样会降低对数据的操作效率。
    • **MVCC**:在需要保证前后两次读的数据一致情况下可以在读取数据时,可以在修改数据时建立一个备份(快照),后面的读操作就读取该快照;这种方式称为 **Multi Version Concurrency Control** 多版本的并发控制。**MVCC** 主要思想是查询在 **TransactionA** 开始之间已经存在的数据,即使它在后面已经被修改或删除,在 **TransactionB** 之后的新增数据是查询不到的。而且在 **InnoDB** 存储引擎为每行记录都实现了两个隐藏字段对快照的创建时间与保证读取到的是快照而不是最新的数据问题解决:
      • **DB_TRX_ID**:表示事务 ID**6 byte**),在插入或更新行的最后一个事务的事务 **ID**,该 **ID** 是自动递增的;也可以理解为创建版本号,当数据新增或修改为新数据时就记录当前的事务 **ID**
      • **DB_ROLL_PTR**:表示回滚指针(**7 byte**),可以理解为删除版本号,当数据被删除或记录为旧数据时就记录当前事务 **ID**
      • **DB_ROW_ID**:前面说过当没有定义主键和满足条件的唯一主键时,在 **InnoDB** 中会有该隐藏字段。
  • **MVCC** 模拟: 其查询规则是只能查找 **DB_TRX_ID < Current_DB_TRX_ID | DB_ROLL_PTR > DB_TRX_ID**,也就是不能查询在事务开始之后插入的数据,Ken**DB_TRX_ID** 大于 **2**,所以还是只能查到两条数据。
    • **TransactionA** 初始化数据
-- 开启事务
BEGIN;
	-- 插入两条数据
	INSERT INTO user VALUES ( NULL, 'John' );
	INSERT INTO user VALUES ( NULL, 'Sky' );
-- 提交事务
COMMIT;
  • 此时的数据中的创建版本是当前的事务 **ID**,删除版本为空;

id

name

DB_TRX_ID

DB_ROLL_PTR

1

John

1

undefined

2

Sky

1

undefined

  • **TransactionB** 开启事务第一次查询数据,但不提交事务;读取到两条数据,此时的事务 **ID = 2**
-- 开启事务
BEGIN;
	-- (1) 第一次查询
	SELECT *  FROM user;

id

name

DB_TRX_ID

DB_ROLL_PTR

1

John

1

undefined

2

Sky

1

undefined

  • **TransactionC** 插入一条数据,此时多了一条数据,它的创建版本号是当前事务编号 3
-- 开启事务
BEGIN;
	-- 插入数据
	INSERT INTO user VALUES ( NULL, 'Ken' );
-- 提交事务
COMMIT;

id

name

DB_TRX_ID

DB_ROLL_PTR

1

John

1

undefined

2

Sky

1

undefined

3

Ken

3

undefined

  • **TransactionB** 第二次执行查询语句 **SELECT * FROM user;**

id

name

DB_TRX_ID

DB_ROLL_PTR

1

John

1

undefined

2

Sky

1

undefined

  • **TransactionD** 删除 **id = 1** 的数据;
-- 开启事务
BEGIN;
	-- 删除 id = 1 的数据
	DELETE FROM `user` WHERE id = 1;
-- 提交事务
COMMIT;
  • 此时的数据 **John** 的删除版本记录为当前的事务 **ID = 4**,其他数据不变

id

name

DB_TRX_ID

DB_ROLL_PTR

1

John

1

4

2

Sky

1

undefined

3

Ken

3

undefined

  • **TransactionB** 第三次执行查询语句 **SELECT * FROM user;**
  • 只能查找 **DB_TRX_ID < Current_DB_TRX_ID | DB_ROLL_PTR > DB_TRX_ID** 或未删除,在事务开始之后的删除数据还是可以查询出来。

id

name

DB_TRX_ID

DB_ROLL_PTR

1

John

1

undefined

2

Sky

1

undefined

  • **TransactionE** 执行更新操作,这个的事务 **ID = 5**
-- 开启事务
BEGIN;
	-- 更新 id = 2 的数据
	UPDATE `user` SET `name` = 'Jack' WHERE id = 2;
-- 提交事务
COMMIT;
  • 此时的数据在更新数据时会把旧数据的删除版本被记录为当前事务的 **ID = 5(undo)** 产生了一条新数据,DB_TRX_ID = Current_DB_TRX_ID = 5

id

name

DB_TRX_ID

DB_ROLL_PTR

1

John

1

4

2

Sky

1

undefined

3

Ken

3

5

3

Jack

5

undefined

  • **TransactionB** 第四次执行查询语句 **SELECT * FROM user;**
  • 只能查找 **DB_TRX_ID < Current_DB_TRX_ID | DB_ROLL_PTR > DB_TRX_ID** 或未删除,
    • 因为在更新后的数据 **Jack****DB_TRX_ID > 2** 代表着在事务之后增加的,所以查询不出来;
    • 而旧版本 **Ken****DB_ROLL_PTR > 2** 代表着事务之后删除的,可以查询出来;
    • 通过案例可以看到通过版本号的控制,其他事务的插入、修改和删除操作在 **TransactionB** 几次查询到的数据都没有发生变化。

id

name

DB_TRX_ID

DB_ROLL_PTR

1

John

1

undefined

2

Sky

1

undefined

  • **InnoDB** 存储引擎中的 **MVCC** 是通过 **undo log** 实现的;但 **MVCC** 和锁是协同使用的,这两种方案并不是互斥的。

InnoDB 锁

锁的粒度

  • **InnoDB** 中的表锁是锁住整张表,而行锁是锁住表中的一行数据;所以表锁的粒度大于行锁;
  • 对于加锁的效率表锁也是大于行锁的,因为表锁只是直接锁住整张表;而行锁还需要在表中检索这一行数据,所以表锁的加锁效率更高;
  • 对于冲突的概率表锁是大于行锁的,因为当锁住整张表时,其他任何一个事务都不能操作这张表;但只是使用了行锁去锁住表中的一行数据的时,其他事务还可以来操作表里面的其他没有被锁定的行,所以表锁的冲突概率更大;
  • 表锁的冲突概率大,所以并发性能低。

共享锁

  • 共享锁也是行级锁(读锁), 在获取一行数据的读锁后可以用来读取数据;在加上读锁后不要去写数据,可能会出现死锁的情况。多个事务可以共享一把读锁,一般情况下在提交事务或结束事务就可以释放锁。读锁是可以重复获取的。

TransactionA

TransactionB

BEGIN;

SELECT * FROM user LOCK IN SHARE MODE;

BEGIN;

SELECT * FROM user LOCK IN SHARE MODE;

排它锁

  • 排它锁也属于行级别锁的一种(写锁),用于对数据的操作;当一个事务获取一行数据的排他锁,其他的事务就不能再获取这行数据的共享锁和排它锁。一般可以自动加排它锁和手动地加排它锁。释放锁是和读锁一致。
    • 自动加排它锁:在操作数据时都会默认地加上一个排他锁;
    • 使用 **FOR UPDATE** 给一行数据手动地加排它锁。

TransactionA

TransactionB

BEGIN;

UPDATE user SET name=‘Jack’ WHERE id = 1;

BEGIN;

– 下面操作都是阻塞的

SELECT * FROM user LOCK IN SHARE MODE; ** SELECT * FROM user FOR UPDATE;** ** DELETE FROM user WHERE id = 1;** |

image.png
image.png

意向锁

  • 意向锁是由数据库自己维护的,当为一行数据加上共享锁(排它锁)之前,数据库会自动在这张表上加一个意向共享锁(排它锁);如果一张表上至少有一个意向共享锁(排它锁),说明有其他的事务给其中的某些数据行加上了共享锁(排它锁)。
  • 这两个锁主要作用是在 **InnoDB** 中就可以支持更多粒度的锁;以及提高加锁的效率,如果没有意向锁的情况下准备给一张表加上表锁时必须先判断是否有其他事务锁定了其中的某行,有的情况下就不能加锁;也就是说需要去扫描整张表才能确定是否加表锁,当数据量特别大时,加锁的效率就很低。在引入意向锁后只是需要判断这张表上是否有意向锁,如果有就返回失败,否则就加锁成功。在 **InnoDB** 里面的表锁可以理解成一个标志,这就是提高加锁效率的原因。

TransactionA

TransactionB

BEGIN;

SELECT * FROM user FOR UPDATE;

BEGIN;

– 阻塞

** LOCK TABLES user WRITE;** – 释放锁 ** UNLOCK TABLES;** |

记录锁

  • 当对唯一索引或主键索引使用等值查询并精确地匹配到一条记录时,就是使用的记录锁,在对不同的主键进行加锁时是不会冲突的。案例演示
image.png
image.png

间隙锁

  • 当查询的记录不存在且没有命中任何一个记录时,无论使用等值查询还是范围查询时都是使用的间隙锁;比如:**where id > 4 and id < 7 | where id = 6**;间隙锁主要是阻塞插入,相同的间隙锁之间不冲突。 **Gap Lock** 只是在 **RR** 中存在,如果要关闭间隙锁就是把事务隔离级别设置成 **RC** 且设置 **innodb_locks_unsafe_for_binlog = ON**
image.png
image.png

TransactionA

TransactionB

BEGIN;

select * from t2 where id = 6 for update;

select * from t2 where id > 20 for update;

BEGIN;

– 执行 where id = 6 时阻塞

INSERT INTO t2 (id, name) VALUES (5, ‘5’); – 执行 where id = 6 时阻塞 ** INSERT INTO t2 (id, name) VALUES (6, ‘6’);** – 执行 where id > 20 时阻塞 INSERT INTO t2 (id, name) VALUES (11, ‘11’); – 不阻塞,因为相同间隙锁不冲突 select * from t2 where id = 6 for update; |

临建锁

  • 当使用了范围查询,不仅命令中了记录,还包含了间隙,在这种情况下使用的就是临键锁,它是 **MySQL** 里默认的行锁算法,相当于记录锁加上了间隙锁。当唯一性索引在等值查询匹配到一条记录时,会退化成记录锁,在没有匹配到任何记录时,会退化成间隙锁。例如:**where id > 5 and < 9**,它包含了记录不存在的区间,也包含了一个 **Record 7**。临键锁锁住的是最后一个 **key** 的下一个左开右闭的区间,这样是为了解决幻读的问题。
image.png
image.png

TransactionA

TransactionB

BEGIN;

锁住的范围是 (5, 10]

select * from t2 where id > 5 and id < 9 for update;

BEGIN;

** – 因为锁住是 (5, 10],Non-Block**

SELECT * FROM t2 WHERE id = 4 FOR UPDATE; – 因为锁住是 (5, 10],Block INSERT INTO t2 (id, name) VALUES (6, ‘6’); – 因为锁住是 (5, 10],Block INSERT INTO t2 (id, name) VALUES (8, ‘8’); – 因为锁住是 (5, 10],Block SELECT * FROM t2 WHERE id = 10 for update; |

行锁的原理

没有索引的表

  • 在没有索引的表去使用 **InnoDB** 的锁是锁住的整张表,而不是具体行;现在创建一个无索引的表且手工的在两个会话中开启两个事务,在 **TransactionA** 中锁住 **where id = 1** 的数据,然后在 **TransactionB** 中尝试给 **where id = 2** 的记录加锁时被阻塞了;接着再插入一条不存在的数据也发现被阻塞了。所以在没有索引的表在 **InnoDB** 中是锁住整张表的,而不是 **Record**

TransactionA

TransactionB

BEGIN;

SELECT * FROM user where id = 1 FOR UPDATE;

BEGIN;

– 阻塞

SELECT * FROM user WHERE id = 2 FOR UPDATE;

– 阻塞 ** INSERT INTO user VALUES(4, ‘Jack’);** |

image.png
image.png

有主键索引的表

  • 在有主键索引的表去使用 **InnoDB** 的锁是锁住的是具体行,在使用相同 **id** 值去加锁会出现冲突;使用不同的 **id** 去加锁就可以加锁成功

TransactionA

TransactionB

BEGIN;

SELECT * FROM user where id = 1 FOR UPDATE;

BEGIN;

– 阻塞

SELECT * FROM user WHERE id = 1 FOR UPDATE; – 成功 ** SELECT * FROM user WHERE id = 2 FOR UPDATE;** |

image.png
image.png

有唯一索引的表

  • 在有唯一索引的表去使用 **InnoDB** 的锁是锁住的是索引,在 **id is primary key & name is unique key** 的情况下;首先在 **TransactionA** 中通过 **name = 'John'** 去进行加锁,然后在 **TransactionB** 中获取一样的锁会失败。然后使用 **id = 1** 的去加锁发现也是会失败的。

TransactionA

TransactionB

BEGIN;

SELECT * FROM user where name = ‘John’ FOR UPDATE;

BEGIN;

– 阻塞

SELECT * FROM user where name = ‘John’ FOR UPDATE; – 阻塞 ** **SELECT * FROM user where id = 1 FOR UPDATE; |

image.png
image.png
  • 当表里没有索引时,锁住一行数据会导致锁表或在锁住的是索引,而一张表里没有索引情况时的问题:
    • 当定义了 **Primary Key** 时,**InnoDB** 存储引擎会选择主键作为聚集索引;
    • 当没有显示定义主键时,**InnoDB** 存储引擎会选择第一个不含有 **NULL** 值的唯一索引作为主键索引;
    • 当没有这样的唯一索引时,**InnoDB** 存储引擎会选择内置 **6** 个字节长的 **ROWID** 作为隐藏的聚集索引,它会随着行记录的写入而主键递增。
    • 所以锁表的原因是因为查询没有使用索引就会进行全表扫描,然后把每个隐藏的聚集索引都给锁住。
  • 当通过唯一索引给数据行加锁时,主键索引也会被锁住的原因是在辅助索引里存储的是二级索引和主键的值,而主键索引里除了索引外,还存储了完整的数据。所以在通过辅助索引锁定一行数据时,它和我们检查数据的步骤是一样的,会通过主键值找到主键索引,然后锁定。

死锁

  • 在一般情况下锁在事务结束或客户端断开连接后就释放;当一个事务一直未释放锁,当在并发访问比较高的情况下有大量的事务因为无法立即获得所需的锁而挂起,这回占用大量的计算机资源,造成严重的性能问题,甚至拖垮数据库。在 **MySQL** 中有一个参数控制获取锁的等待时间,默认是 **50m**
show VARIABLES like 'innodb_lock_wait_timeout';
  • 演示死锁,在 **TransactionA** 中检测到了死锁就马上退出了;在 **TransactionB** 不需要等待 **50m **就可以获取到锁。由于死锁的发送需要满足一定的条件,当死锁发生时,一般情况在 **InnoDB** 中可以通过算法 **wait-for graph** 自动检测到。那么导致死锁的产生条件有:
    • 因为锁是互斥的,在同一时间只能有一个事务持有这把锁;
    • 其他的事务需要在这个事务释放锁之后才能获得,而不能强行获取;
    • 当多个事务形成等待环路时就发生了死锁。

TransactionA

TransactionB

BEGIN;

SELECT * FROM t2 WHERE id = 1 FOR UPDATE;

BEGIN;

DELETE FROM t2 WHERE id = 4 ;

** – 执行完后,执行 Session 2 中的第二句会马上退出**

** UPDATE t2 SET name = ‘test_dead_lock’ WHERE id = 4;**

– 出现死锁,Session 1 退出,获取到锁

DELETE FROM t2 WHERE id = 1 ;

  • 当锁一致没有释放,就可能造成大量阻塞或发生死锁,造成系统吞吐量下降;这时就需要查看哪些事务持有了锁。
  • 查看锁信息:
-- Innodb_row_lock_current_waits: 当前正在等待锁定的数量;
-- Innodb_row_lock_time: 从系统启动到现在锁定的总时间长度,单位 ms;
-- Innodb_row_lock_time_avg : 每次等待所花平均时间;
-- Innodb_row_lock_time_max: 从系统启动到现在等待最长的一次所花的时间;
-- Innodb_row_lock_waits : 从系统启动到现在总共等待的次数;
show status like 'innodb_row_lock_%';

-- 查看当前运行的所有事务和具体的语句
select * from information_schema.INNODB_TRX;

-- 查看当前出现的锁
select * from information_schema.INNODB_LOCKS;

-- 查看锁等待的对应关系
select * from information_schema.INNODB_LOCK_WAITS;
  • 当一个事务长时间持有锁不释放,可以 **kill** 事务对应的线程 **ID**;也尽量的在应用端编码的过程中避免死锁。
  • 死锁的避免:
    • 在程序中操作多张表时,尽量以相同的顺序来访问(避免形成等待环路);
    • 批量操作单张表数据时,先对数据进行排序(避免形成等待环路);
    • 申请足够级别的锁,当要操作数据时就申请排它锁;
    • 尽量使用索引访问数据,避免没有 **where** 条件的操作,避免锁表;
    • 如果可以,就把大事务化小事务;
    • 使用等值查询而不是范围查询数据,命中记录,避免间隙锁对并发的影响。
image.png
image.png
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-02-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 数据库的事务
    • 事务的定义
      • 事务的四大特性
        • 数据库出现事务的场景
          • 事务在并发时带来的问题
            • SQL92 标准
              • MySQL InnoDB 中对隔离级别的支持
                • 解决读一致性问题的方式
                • InnoDB 锁
                  • 锁的粒度
                    • 共享锁
                      • 排它锁
                        • 意向锁
                          • 记录锁
                          • 间隙锁
                          • 临建锁
                        • 行锁的原理
                          • 没有索引的表
                          • 有主键索引的表
                          • 有唯一索引的表
                        • 死锁
                        相关产品与服务
                        云数据库 SQL Server
                        腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
                        领券
                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档