一、事务
事务与ACID属性
原子性(Atomicity)、一致性(Consistent)、隔离性(Isolation)、持久性(Durable)
并发事务处理带来的问题
更新丢失:两个事务同时操作相同的数据,后提交的事务会覆盖先提交的事务处理结果,通过乐观锁就可以解决
脏读:事务A读取到了事务B已经修改但尚未提交的数据,如果B事务回滚,A读取的数据无效,不符合一致性。
不可重读:事务A读取了事务B已经提交的修改数据,不符合隔离性。
幻读:事务A读取到了事务B提交的新增数据,不符合隔离性
事务隔离级别
1.查看数据库事务隔离级别:show variables like 'tx_isolation';
2.设置事务隔离级别:set tx_isolation='REPEATABLE-READ';
ReadUncommitted:进程B可以从进程A读取未提交的数据,并且基于B的写入可以看到不同的行。完全没有锁
ReadCommitted:进程B只能从进程A读取已提交的数据,并且基于仅COMMITTED B的写入,它可以看到不同的行。(一个事务读取数据时总是读这个数据最近一次被commit的版本)
RepeatableRead:无论进程A在做什么,进程B都将读取相同的数据(行)。但是进程A可以更改其他行。行级块。(一个事务读取数据时总是读取当前事务开始之前最后一次被commit的版本)
Serializeable:进程B将读取与以前相同的行,并且进程A无法在表中读取或写入。表级块
默认是可重复度读级别(RepeatableRead),客串行化(Serializable)性能很低
二、锁分类
锁定义
锁是计算机协调多个进程或线程并发访问某一资源的机制。分为表锁(MyISAM)和行锁 (Innodb)
行锁(特性)
开销大、加锁慢、会出现死锁、锁定粒度小、并发高、偏向InnoDB存储引擎(支持事务)、发生锁冲突的概率最低
在InnoDB事务中,行锁是在需要时才加上的,但不是不需要了就立刻释放,而要等事务结束时才释放。
如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放。
表锁(特性):
开销小、加锁快、锁定粒度大、并发度最低、偏向MyISAM存储引擎(不支持事务)、发生锁冲突的概率最高
表锁(操作命令):
加表锁: lock table 表名称 read(write),表名称2 read(write);
查看表锁:show open tables;
删除表锁:unlock tables;
总结:
MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行增删改操作前,会自动给设计的表加写锁。
读锁会阻塞写,但不会阻塞读。
写锁则会吧读和写都阻塞。
三、锁
InnoDB行锁可细分为记录锁(Record Lock)、间隙锁(Gap Lock)、临键锁(Next_Key_Lock),是基于索引实现的,其本质是三种加锁算法。(若不声明,默认采用RR隔离级别)
这三种并不是锁,而是锁的算法。它们的共同特点是互斥的。间隙锁和临键锁只有在RR级别中才能生效。
共享锁
又名读锁、S锁,属于悲观锁。当事务A在执行读锁未提交时,事务B可以进行读锁,不可以进行写锁。select不加锁
使用场景:读取数据后,其他事务不能修改,但是自己也不一定能修改,因为其他事务也可以使用" select ....lock in share mode "继续加读锁。
排他锁
又名写锁、X锁,属于悲观锁。当事务A在执行写锁未提交时,事务B不可以进行写锁和读锁。update、insert、delete自带写锁。
使用场景:读取数据后,其他事务即不能写,也不能加读锁,那么就导致只有自己可以修改数据。
select ... for update 查询加写锁
delete:删除一条数据时,先对记录加X锁,再执行删除操作。
insert:插入一条记录时,会先加隐式锁来保护这条新插入的记录再本事务提交前不被别的事务访问到。
隐式锁:一个事务插入一条记录后,还未提交,这条记录会保存本次事务id,而其他事务如果想来对这个记录加锁时会发现事务id不对应,这时会产生写锁,所以相当于再插一条记录时,隐式的给这条记录加一把隐式的写锁。
update:如果被更新的列,修改前后没有导致存储空间变化,那么会先给记录加写锁,再直接对记录进行修改。
如果被更新的列,修改前后导致存储空间变化,那么会先给记录加写锁,然后将记录删除掉,再insert一条新记录。
意向共享锁
又名IS。通知数据库接下来需要施加什么锁并对表加锁。如果需要对记录A加共享锁,那么此时innodb会先找到这张表,对该表加意向共享锁之后,再对记录A添加共享锁(都是系统自动添加和自动释放的,整个过程无需人工干预
)。
意向排他锁
又名IX。通知数据库接下来需要施加什么锁并对表加锁。如果需要对记录A加排他锁,那么此时innodb会先找到这张表,对该表加意向排他锁之后,再对记录A添加排他锁(都是系统自动添加和自动释放的,整个过程无需人工干预
)。
自增锁
已执行一条插入自增数据id=10,发生了回滚。这时再次执行新增数据将从id=11开始,虽然id=10并不存在。
如果存在自增字段,MySQL会维护一个自增锁,和自增锁相关的一个参数(5.1.22版本之后加入)
记录锁(Record Lock)
锁精确加在某一行上。一般要通过主键或唯一索引加锁。
select * from fruit where id = 50 for update; # 记录锁
id 为 50 的记录行会被锁住
需要注意的是:id
列必须为唯一索引列
或主键列
,否则上述语句加的锁就会变成临键锁
。
同时查询语句必须为精准匹配
(=
),不能为 >
、<
、like
等,否则也会退化成临键锁。
间隙锁(Gap Locks)
间隙锁基于非唯一索引
,它锁定一段范围内的索引记录
。间隙锁基于下面将会提到的Next-Key Locking
算法,请务必牢记:使用间隙锁锁住的是一个区间,而不仅仅是这个区间中的每一条数据
SELECT
*
FROM
table
WHERE
id BETWEN 1
AND
10
FOR
UPDATE
;
即所有在(1,10)
区间内的记录行都会被锁住,所有id 为 2、3、4、5、6、7、8、9 的数据行的插入会被阻塞,但是 1 和 10 两条记录行并不会被锁住。
除了手动加锁外,在执行完某些 SQL 后,InnoDB 也会自动加间隙锁,这个我们在下面会提到。
临键锁(Next-Key-Locks)
记录锁与间隙锁组合起来用就叫做Next-Key Lock,就是将键及其两边的的间隙加锁(Next-Key 可以理解为一种特殊的间隙锁,也可以理解为一种特殊的算法。通过临建锁可以解决幻读
的问题)。
每个数据行上的非唯一索引列
上都会存在一把临键锁,当某个事务持有该数据行的临键锁时,会锁住一段左开右闭区间的数据。
需要强调的一点是,InnoDB
中行级锁
是基于索引实现的,临键锁只与非唯一索引列
有关,在唯一索引列
(包括主键列
)上不存在临键锁。
自增锁(AUTO-INC LOCKS)
如果存在自增字段,MySQL会维护一个自增锁,和自增锁相关的一个参数为(5.1.22版本之后加入)
innodb_autoinc_lock_mode:可以设定3个值,0,1,2。
0:traditonal (每次都会产生表锁) 1:consecutive (会产生一个轻量锁,simple insert会获得批量的锁,保证连续插入) 2:interleaved (不会锁表,来一个处理一个,并发最高)