MySQL事务与锁

概述

事务是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作系列构成。

这里面有两个关键点:
第一个,它是数据库最小的工作单元,是不可以再分的。
第二个,它可能包含了一个或者一系列的 DML 语句,包括 insert delete update。

InnoDB存储引擎支持事务,MyISAM存储引擎不支持事务。

事务四大特性
  • 原子性:Atomicity,也就是上面说的最小工作单元,不可再分,也就是说这个工作单元中的多个数据库操作,要么全部成功,要么全部失败,不可能出现部分成功或者部分失败的情况。比如转账,设计一个账号余额增加,一个账号余额减少,这两个必须同时成功或者同时失败,不会出现转账者余额减少而被转账者余额不变的情况。
    把成功的操作撤销是通过回滚来实现的,在 InnoDB 里面是通过 undo log 来实现的,它记录了数据修改之前的值(逻辑日志),一旦发生异常,就可以用 undo log 来实现回滚操作。
  • 一致性:consistent,指的是数据库的完整性约束没有被破坏,事务执行前后的状态都是合法的(符合预期,符合业务)的数据,比如转账者减少100余额,被转账者就要增加100余额,两者余额相加总是不变的状态,这是符合预期地,符合业务的数据状态。如果转账者减少100,被转账者只增加了50,这是不符合一致性的。
  • 隔离性:Isolation,我们有了事务的定义以后,在数据库里面会有很多的事务同时去操作我们的同一张表或者同一行数据,必然会产生一些并发或者干扰的操作,那么我们对隔离性的定义,就是这些很多个的事务,对表或者行的并发操作,应该是透明的,互相不干扰的。通过这种方式,我们最终也是保证业务数据的一致性。
  • 持久性:Durable,我们对数据库的任意的操作,增删改,只要事务提交成功,那么结果就是永久性的,不可能因为我们系统宕机或者重启了数据库的服务器,它又恢复到原来的状态了。这个就是事务的持久性。

原子性,隔离性,持久性,最后都是为了实现一致性。

数据库什么时候出现事务

无论是我们在 Navicat 的这种工具里面去操作,还是在我们的 Java 代码里面通过API 去操作,还是加上@Transactional 的注解或者 AOP 配置,其实最终都是发送一个指令到数据库去执行,Java 的 JDBC 只不过是把这些命令封装起来了。

update student set sname = ‘YeHaocong’ where id=1;
实际上,上面sql它自动开启了一个事务,并且提交了,所以最终写入了磁盘。这个是开启事务的第一种方式,自动开启和自动提交。

InnoDB 里面有一个 autocommit 的参数,它的默认值是 ON,如果设置的值是true/on,我们在操作数据的时候,会自动开启一个事务,和自动提交事务,否则,如果我们把 autocommit 设置成 false/off,那么数据库的事务就需要我们手动地去开启和手动地去结束。

SHOW VARIABLES LIKE ‘autocommit’; 使用该命令查看是否开启自动提交事务。

使用begin;开启一个事务,使用commit;提交一个事务,使用rollback;回滚一个事务。提交回滚都会结束事务。还有一种情况,客户端的连接断开的时候,事务也会结束。

当我们结束一个事务的时候,事务持有的锁就会被释放,无论是提交还是回滚。

测试:
注意:因为事务是会话级别的,所以一定要开启两个会话连接来测试,而不能仅仅在一个会话里面开启两个查询页面。否则这两查询页面是属于同一个事务的。

在自动提交开启时,在一个会话里面修改了某个字段值:
UPDATE user SET age=30 WHERE id=1
然后在另一个会话内查询该值:
SELECT *FROM user WHERE id=1 发现数据被修改了,因为事务自动提交了。
在这里插入图片描述
就算你手动使用begin开启事务的情况下,它也会自动提交的。
如果关闭事务自动提交:
SET GLOBAL autocommit = OFF; //全局修改
set autocommit = OFF; //会话级别修改

begin; //手动开启事务
UPDATE user SET age=23 WHERE id=1;

去另外一个会话
begin;
SELECT *FROM user WHERE id=1
结果:
在这里插入图片描述
这个会话查询到的age并不是23,因为会话1的事务没有提交,而mysql默认的隔离级别不允许读取到其他会话未提交的数据。

会话1提交:
commit;
然后会话2再次查询该数据:
在这里插入图片描述
还是没有变化,因为mysql的默认隔离级别为可重复读,所以当事务开启后,对同一行数据读取到的结果总是一样的。要不一样,就要提交当前事务再重新开启事务。

事务2提交:
commit;
begin; 重新开启
SELECT *FROM user WHERE id=1 重新查询
结果:
在这里插入图片描述
ok!

事务并发会带来的问题

当很多事务并发地去操作数据库的表或者行的时候,如果没有我们刚才讲的事务的Isolation 隔离性的时候,会带来哪些问题呢?

脏读

在这里插入图片描述
我们有两个事务A、B,在事务A里面,它首先通过查询id为1的数据,查询到age=18,然后事务B对它进行修改成了20,但是事务B没有提交事务,然后事务A再次查询,却读到了20,也就是读到了事务B没有提交的数据,当事务B因为某些原因进行回滚,这时,数据就产生了不一致性。这种情况就是脏读,读到了其他事务未提交的数据。

不可重复读

在这里插入图片描述
这个与上面的脏读的区别是脏读是读到了其他事务未提交的数据,而不可重复读读到了其他事务已提交的数据。
这种一个事务读取到了其他事务已提交的数据导致前后两次读取数据不一致的情况,我们把它叫做不可重复读。

如果在一个业务事务里读了两次该数据,就可能因为数据不同导致处理逻辑出现差异,虽然还想遇到过这样的场景。

幻读

在这里插入图片描述

事务A使用age>10,查出了一条数据,然后事务B插入了一条数据,age=26,并提交了,然后事务A再次查询,发现查询到了两条数据,多了一条。

一个事务前后两次读取数据数据不一致,是由于其他事务插入数据造成的,这种情况我们把它叫做幻读

不可重复读是修改或者删除,幻读是插入。

上面的事务并发带来的三大问题,总结一下。无论是脏读,还是不可重复读,还是幻读,它们都是数据库的读一致性的问题,都是在一个事务里面前后两次读取出现了不一致的情况。

读一致性的问题,必须要由数据库提供一定的事务隔离机制来解决。就像我们去饭店吃饭,基本的设施和卫生保证都是饭店提供的。那么我们使用数据库,隔离性的问题也必须由数据库帮助我们来解决。

数据库的隔离级别:

从上往下,隔离级别越来越高,性能越来越低:

  • Read Uncommitted(未提交读),一个事务可以读取到其他事务未提交的数据,会出现脏读,所以叫做 RU,它没有解决任何的问题。
  • Read Committed(已提交读),也就是一个事务只能读取到其他事务已提交的数据,不能读取到其他事务未提交的数据,它解决了脏读的问题,但是会出现不可重复读的问题。
  • Repeatable Read (可重复读),它解决了不可重复读的问题,也就是在同一个事务里面多次读取同样的数据结果是一样的,但是在这个级别下,没有定义解决幻读的问题。
  • Serializable(串行化),在这个隔离级别里面,所有的事务都是串行执行的,也就是对数据的操作需要排队,已经不存在事务的并发操作了,所以它解决了所有的问题。

读未提交在大部分场景都会导致数据一致性问题,串行化的效率太低,所以一般都会在已提交读和可重复读里面选择。具体看业务场景有没有会因为不可重复读导致数据一致性的问题的出现,可能就要把隔离级别上升到可重复读。

InnoDB对隔离级别的支持

在 MySQL InnoDB 里面,不需要使用串行化的隔离级别去解决所有问题。那我们来看一下 MySQL InnoDB 里面对数据库事务隔离级别的支持程度是什么样的。

在这里插入图片描述
InnoDB 的可重复读使用了锁,保证了不会出现幻读的问题,具体后面讲锁的时候会说到。

InnoDB 支持的四个隔离级别和 SQL92 定义的基本一致,隔离级别越高,事务的并发度就越低。唯一的区别就在于,InnoDB 在 RR 的级别就解决了幻读的问题。这个也是InnoDB 默认使用 RR 作为事务隔离级别的原因,既保证了数据的一致性,又支持较高的并发度。所以InnoDB根本就用不上串行化的隔离级别。

SHOW VARIABLES LIKE ‘%transaction_iso%’ 查看数据库的隔离级别
在这里插入图片描述
mysql默认是可重复读,InnoDB的可重复读解决了 脏读,不可重复读、幻读三个问题,基本上是解决了数据库并发度的问题了。

可重复读的演示例子上面有。

锁的类型

官网把锁分成了八类,根据锁的粒度和级别,分为行级共享锁,行级排它锁,表级共享锁,表级排他锁,称为锁的基本模式。

Record Locks(记录锁)、Gap Locks(间隙锁)、Next-Key Locks,我们把它们叫做锁的算法,也就是分别在什么情况下锁定什么范围。

锁的粒度

InnoDB既支持行级锁,也支持表锁,而MyISAM只支持表锁。

行锁顾名思义就是锁住某一行记录的锁,表锁是锁住整个表的锁。

锁的粒度 : 行锁<表锁,因为行锁只是锁表中的某一条数据,而表锁是锁住整张表的所有数据。
锁的加锁效率:行锁<表锁,因为表锁,直接锁表就行了,而行锁还要从表中检索出指定资源来锁。
锁的冲突概率:行锁<表锁,因为表锁锁整张表,所以冲突概率自然比行锁大。
锁的并发性能: 行锁>表锁,因为表锁锁整张表,而行锁只锁一行,其他行还可以继续被操作,所以自然并发效率比表锁高。

锁的模式

InnoDB支持共享锁和排他锁:

共享锁

共享锁称为读锁,我们获取了一行数据的读锁以后,可以用来读取数据,所以它也叫做读锁,注意不要在加上了读锁以后去写数据,不然的话可能会出现死锁的情况。而且多个事务可以共享一把读锁。

结束事务就会自动释放锁,无论是提交还是回滚。
使用
lock in share mode 获取一把读锁。

验证1:

会话1会话2
begin;
SELECT *FROM user WHERE id=1 LOCK IN SHARE MODE
begin;
SELECT *FROM user WHERE id=1 LOCK IN SHARE MODE 不阻塞,能够查询到数据,表名对同一行的数据,多个事务可以同时获取到共享锁

验证2:

会话1会话2
begin;
SELECT *FROM user WHERE id=1 LOCK IN SHARE MODE
begin;
UPDATE user SET age =25 WHERE id=1 当前操作被阻塞了,表名对一行的数据上读锁后,其他事务不允许对该行进行修改
commit上面的修改操作取消阻塞,执行完成,因为会话1的提交操作释放了读锁,会话2就能修改该行数据了
排它锁

排他锁又称为写锁,一旦上锁,其他事务无论如何都不能对该数据进行访问,无论是修改还是读取。

排它锁的加锁方式有两种:
第一种是自动加排他锁。我们在操作数据的时候,包括增删改,都会默认加上一个排它锁。

还有一种是手工加锁,我们用一个 FOR UPDATE 给一行数据加上一个排它锁,这个无论是在我们的代码里面还是操作数据的工具里面,都比较常用。

验证1:
主要验证给一行数据上了排他锁,其他事务就不能获取该数据的排它锁(不能修改该行数据):

会话1会话2
begin;
UPDATE user SET age =19 WHERE id=1
begin;
UPDATE user SET age =15 WHERE id=1 当前修改操作被阻塞,因为会话1已经对该行数据上了把排他锁,并且没有释放,所以会话2就阻塞,直到会话1释放锁
commit;上面修改操作放行了,不阻塞了,修改成功

验证 2:
主要验证给一行数据上了排他锁,其他事务就不能获取该数据的共享锁:

会话1会话2
begin;
UPDATE user SET age =19 WHERE id=1
begin;
SELECT *FROM user WHERE id=1 LOCK IN SHARE MODE 当前查询操作被阻塞,因为会话1已经对该行数据上了把排他锁,并且没有释放,所以会话2就阻塞,直到会话1释放锁
commit;上面查询操作放行了,不阻塞了,查询成功

验证3:
给查询语句使用for update上排他锁,测试另一会话能否获取排他锁或者共享锁:

会话1会话2
begin;
SELECT *FROM user WHERE id=1 FOR UPDATE
begin;
SELECT *FROM user WHERE id=1 LOCK IN SHARE MODE 当前查询操作被阻塞,因为会话1已经对该行数据上了把排他锁,并且没有释放,所以会话2就阻塞,直到会话1释放锁
commit;上面查询操作放行了,不阻塞了,查询成功
commit
begin;
SELECT *FROM user WHERE id=1 FOR UPDATE
begin;
UPDATE user SET age =15 WHERE id=1 当前修改操作被阻塞,因为会话1已经对该行数据上了把排他锁,并且没有释放,所以会话2就阻塞,直到会话1释放锁
commit;上面修改操作放行了,不阻塞了,修改成功

不能对修改操作使用for update 上排他锁,会报错,因为这些操作会自动上排他锁的,无需手动。
也不能对修改操作上共享锁,因为写操作必须排他。

意向锁

意向锁是什么呢?我们好像从来没有听过,也从来没有使用过,其实他们是由数据库自己维护的。

当我们给一行数据加上共享锁之前,数据库会自动在这张表上面加一个意向共享锁。

当我们给一行数据加上排他锁之前,数据库会自动在这张表上面加一个意向排他锁。

如果一张表上面至少有一个意向共享锁,说明有其他的事务给其中的某些数据行加上了共享锁。

如果一张表上面至少有一个意向排他锁,说明有其他的事务给其中的某些数据行加上了排他锁。

意向锁有点标志锁的意思。

意向锁的主要作用是提高表锁的加锁效率,如果没有意向锁的话,我们要给一张表加表锁,首先要进行表的全表扫描,看看表中是否有行加了排他锁,如果有,那么 对该表无论是表共享锁还是表排他锁都会加锁失败,也看看表中是否有行加了共享锁,如果有,那么对该表就只能加共享表锁,而无法加表排它锁。如果一个表有上千万条数据,进行全表扫描无疑是效率极其低下的,所以引入了意向锁,如果一个表中有意向排他锁(不用管多少个,有至少一个就行),就证明表中有数据行被加上了排他锁,就无法对该表加任何锁,如果该表有意向共享锁,就证明表中有数据行被加上了共享锁,就只能加表共享锁。

验证1:
在有意向排它锁的情况下加表写锁。

会话1会话2
begin;
UPDATE user SET age =18 WHERE id=1
begin;
LOCK TABLES user WRITE; 加表写锁,该操作阻塞住了,因为在会话1中加了排他锁,所以该表会有一个意向排它锁。所以就无法加表写锁,直到上面的锁释放。
commit;阻塞放行,加表写锁成功,因为会话1commit操作释放了锁
UNLOCK TABLES 释放当前会话持有的表锁

验证2:
在有意向排它锁的情况下加表读锁。

会话1会话2
begin;
UPDATE user SET age =18 WHERE id=1
begin;
LOCK TABLES user READ; 加表读锁,该操作阻塞住了,因为在会话1中加了排他锁,所以该表会有一个意向排它锁。所以就无法加表读锁,直到上面的锁释放。
commit;阻塞放行,加表读锁成功,因为会话1commit操作释放了锁
UNLOCK TABLES 释放当前会话持有的表锁

验证3:
在只有意向共享锁的情况下加表读锁。

会话1会话2
begin;
SELECT *FROM user WHERE id=1 LOCK IN SHARE MODE
begin;
LOCK TABLES user READ; 加表读锁,该操作没有阻塞,直接表读锁成功,说明可以在只有意向读锁的情况下加表读锁
commit;
UNLOCK TABLES 释放当前会话持有的表锁

验证4:
在只有意向共享锁的情况下加表排他锁。

会话1会话2
begin;
SELECT *FROM user WHERE id=1 LOCK IN SHARE MODE
begin;
LOCK TABLES user WRITE; 加表写锁,该操作阻塞住了,因为在会话1中加了读锁,所以该表会有一个意向读锁。所以就无法加表写锁,直到上面的锁释放。
commit;上面的阻塞放行了
UNLOCK TABLES 释放当前会话持有的表锁

反过来:
如果对一个表加了表写锁,就无法对该表任意行加行写锁或者读锁。直至表写锁释放。
如果对一个表加了表读锁,就只能对该表的行加读锁,而不能加写锁。
这里就不一 一验证了。感兴趣可以自行验证。

行锁的原理

行锁只是一种模式,锁定的范围、逻辑要靠锁的算法来决定:
有三种锁算法:
Record Locks(记录锁),Gap Locks(间隙锁),Next-Key Locks(临键锁)。

记录锁

第一种情况,当我们对于唯一性的索引(包括唯一索引和主键索引)使用等值查询,精准匹配到一条记录的时候,这个时候使用的就是记录锁。

对user表的name属性加一个唯一索引。
在这里插入图片描述
有三条记录。
验证:

会话1会话2
begin
UPDATE user SET age =18 WHERE id=1
UPDATE user SET age =18 WHERE id=2 修改成功,没有阻塞。
UPDATE user SET age =18 WHERE id=1 阻塞了,因为在会话1 对该条记录进行了上锁,这证明使用主键索引或者唯一索引进行等值查询时,只会锁查到的哪行记录,唯一索引就不验证了,感兴趣可以自行验证

当我们使用主键索引或者唯一索引来进行等值查询,并且命中了唯一的记录时,就会使用记录锁来锁定这个唯一的记录行,而与其他记录无关。

间隙锁

间隙锁,顾名思义是锁间隙的锁。
当我们查询的记录不存在,没有命中任何一个 record,无论是用等值查询还是范围查询的时候,它使用的都是间隙锁。

比如 where id>4 and id <7 或者 where id=6 这些都是一条记录都没有被命中的情况下,就会触发间隙锁。

间隙是左开右开的集合。

在这里插入图片描述
上图是一个表的某个字段的值,该表只有四条记录,所以该表有五个间隙,分别如上图展示的那样。
间隙锁就是只锁间隙,不锁记录,就是锁不存在的记录,假如上述字段是主键索引字段的话。
如果使用where id=100 来锁定某条记录的话,因为该记录不存在,所以会触发间隙锁,
因为100在(9,正无穷)这个间隙,所以,间隙锁会对这个间隙上锁。
如果where id>7 and where id<9 ,范围查询,但是查询不出一条记录,就回触发间隙锁,锁住(6,9)这个间隙。

验证:

会话1会话2
begin;
UPDATE user SET age =18 WHERE id=1111
begin;
INSERT INTO user (id,NAME,age,location,gender) VALUES (10,‘LiaoXiaoyan’,20,‘赤岗’,0) 该操作被阻塞了,因为会话1修改id为1111数据,由于数据不存在,触发间隙锁,锁定了(9,正无穷)这个间隙,而id为10在里面,所以就会被阻塞住

间隙锁主要是阻塞插入 insert。相同的间隙锁之间不冲突。
Gap Lock 只在 RR 中存在。如果要关闭间隙锁,就是把事务隔离级别设置成 RC,并且把 innodb_locks_unsafe_for_binlog 设置为 ON。这种情况下除了外键约束和唯一性检查会加间隙锁,其他情况都不会用间隙锁。

临键锁

当我们使用了范围查询,不仅仅命中了 Record 记录,还包含了 Gap间隙,在这种情况下我们使用的就是临键锁,它是 MySQL 里面默认的行锁算法,相当于记录锁加上间隙锁。即锁记录,也锁间隙。

其他两种退化情况:
唯一性索引,等值查询匹配到一条记录的时候,退化成记录锁。
没有匹配到任何记录的时候,退化成间隙锁。

临键锁,锁住最后一个 key所在左开右闭区间 和该key的下一个左开右闭的区间。并且还会锁住命中的所有行。
在这里插入图片描述
临键是左开又必的集合。
比如一条语句
select *from user where id>2 and id<9 for update 他就锁住了id>2 ,并且因为最后一个命中的key是7,所以他还会把7的下一个区间也锁住,也就是(7,10]。 所以 实际上锁住了 (2,10]这段区间。
这样理解可能不好理解。

使用临键锁可以解决幻读问题,这也是为什么在InnoDB引擎,RR隔离级别就可以解决幻读的原因,就是临建锁解决的,怎么解决的呢。

比如在一个事务中,使用了该语句:
select *from user where id>2 and id<9 for update,他就锁住了 (2,10]这个区间,select *from user where id>2 and id<9只会查出id为3,4,5,6,7,8这几个id的记录,而这些记录都被间隙锁锁住了,无法insert记录,所以就不会出现幻读的现象,
通过这个,就可以反向理解间隙锁锁住的范围了,就是查询、修改的范围和最后一个key的下一个区间的并集。
比如where id>2 and id<9 他的查询返回是(2,9) ,最后一个key的下一区间是(7,10],两者一合并,就是(2,10],这样可能比较好理解。

这里就不验证了,感兴趣可以自行验证。

没有建立索引的列对锁范围的影响

age列是没有建立索引的。
会话1先执行以下命令:
begin;
SELECT *FROM user WHERE age=20 FOR UPDATE

会话2执行以下命令
begin;
SELECT *FROM user WHERE age=16 FOR UPDATE 访问其他存在的记录,被阻塞了。

会话3执行以下命令
begin;
SELECT *FROM user WHERE age=20 FOR UPDATE 就访问该记录,被阻塞了。

会话4执行以下命令
begin;
SELECT *FROM user WHERE age=200 FOR UPDATE 就访问不存在的记录,被阻塞了。

综上所述,使用没有加索引的列来加锁,会阻塞全表。
会话1的SELECT *FROM user WHERE age=20 FOR UPDATE 无论age=2000还是多少,是否存在,都会锁全表。

普通索引对锁范围的影响

往age列加一个普通索引:
会话1:
begin;
SELECT *FROM user WHERE age=20 FOR UPDATE

会话2:
begin;
SELECT *FROM user WHERE age=20 FOR UPDATE 阻塞了

会话3:
begin;
SELECT *FROM user WHERE age=18 FOR UPDATE 查询其他存在的数据,没阻塞。

会话4:
begin;
SELECT *FROM user WHERE age=200 FOR UPDATE 查询其他不存在的数据,没阻塞。

综上,说明使用普通索引来加锁,是跟唯一索引类似,只是因为不是唯一的,可能会锁住多行数据。

limit 对锁范围的影响:

全部数据:
在这里插入图片描述

会话1:
begin;
SELECT *FROM user WHERE age>1 limit 1 FOR UPDATE
查询到一条数据:
在这里插入图片描述
会话2:
begin;
SELECT *FROM user WHERE age=16 limit 1 FOR UPDATE 阻塞了

会话3:
begin;
SELECT *FROM user WHERE age=20 limit 1 FOR UPDATE 没有阻塞

综上:
limit可以有效的减小锁的范围。只有查询到的数据才会加锁。但是对没有使用索引的列,怎么limit都不会减少,还是锁全表。

隔离级别的实现

隔离级别是使用锁来实现的,通过上面,我们队锁都有一定的理解,可以对隔离级别的实现有一定的理解了。

  • 读未提交: 不加锁。
  • 串行化:串行化的所有的 select 语句都会被隐式的转化为 select … in share mode,会和 update、delete 互斥。
  • 可重复读:RR 隔离级别下,普通的 select 使用快照读(snapshot read),底层使用 MVCC 来实
    现。加锁的 select(select … in share mode / select … for update)以及更新操作update, delete 等语句使用当前读(current read),底层使用记录锁、或者间隙锁、临键锁。
  • 读已提交:RC 隔离级别下,普通的 select 都是快照读,使用 MVCC 实现。加锁的 select 都使用记录锁,因为没有 Gap Lock,除了两种特殊情况——外键约束检查(foreign-key constraint checking)以及重复键检查(duplicate-key checking)时会使用间隙锁封锁区间。所以RC会出现幻读问题。
死锁

死锁的发生条件是两个事务都需要对方的资源,并且又占据着对方需要的资源,就发发生死锁。

比如事务1 对行1 上了锁,事务2对行2上了锁,事务1需要行2才能提交解锁,事务2需要行1才能提交解锁。

  1. 事务A执行:
    begin;
    SELECT *FROM user WHERE id=1 FOR UPDATE

  2. 事务2执行:
    begin;
    SELECT *FROM user WHERE id=2 FOR UPDATE

  3. 事务1执行:

SELECT *FROM user WHERE id=2 FOR UPDATE 由于该记录被事务2上了锁并且还没释放,所以阻塞。

  1. 事务2执行:
    SELECT *FROM user WHERE id=1 FOR UPDATE 此时没有阻塞,直接报错重启事务,应该是mysql的一种防止死锁措施。并且事务1的阻塞放行了。

在这里插入图片描述

避免死锁:
1、 在程序中,操作多张表时,尽量以相同的顺序来访问(避免形成等待环路);
2、 批量操作单张表数据的时候,先对数据进行排序(避免形成等待环路);
3、 申请足够级别的锁,如果要操作数据,就申请排它锁;
4、 尽量使用索引访问数据,避免没有 where 条件的操作,避免锁表;
5、 如果可以,大事务化成小事务;
6、 使用等值查询而不是范围查询查询数据,命中记录,避免间隙锁对并发的影响。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值