1. 数据库的三种锁共享锁(读锁/S锁)、排他锁(写锁/X锁)、更新锁(U/锁)
- 如果不使用事务,就用不到锁;
- 读锁:等待已经开始的所有事物提交,然后加上当前事务的锁;当前事务提交之前,其他事务/非事务只能读取,不能修改被锁的记录;
- 写锁:等待已经开始的所有事物提交,然后加上当前事务的锁;当前事务提交之前,其他事务/非事务既不能读取、也不能修改被锁记录;
- 共享锁、排他锁的字面意思比读锁、写锁更形象;
- 在一个事务中,先加了共享锁,然后有可能因为需要升级成排他锁;A、B两个事务对同一条记录加了共享锁,然后因为需要都升级为排他锁,就会互相等待对方先放弃共享锁,造成死锁;
- 更新锁,就是为了防止死锁。更新锁的意思是,虽然不是排他锁,但是马上要升级排他锁,其他事务不能加排他锁/更新锁在这条记录上(允许加共享锁,但是加锁时就明确该共享锁不能升级为排他锁)。
2. 加锁的SQL语句写法:
- 共享锁:
select ··· from ··· where ··· in share mode
- 排他锁:
select ··· from ··· where ··· for update
- 更新锁:不知道
3. 脏读、不可重复读、幻读:
- 脏读:事务B执行过程中修改了数据X,在未提交前,事务A读取了X,而事务B却回滚了,这样事务A就形成了脏读;
- 不可重复读:事务A首先读取了一条数据,然后执行逻辑的时候,事务B将这条数据改变了,然后事务A再次读取的时候,发现数据不匹配了,就是所谓的不可重复读了;
- 幻读:事务A首先根据条件索引得到N条数据,然后事务B改变了这N条数据之外的M条或者增添了M条符合事务A搜索条件的数据,导致事务A再次搜索发现有N+M条数据了,就产生了幻读;
- 不可重复读针对的是update或delete,幻读针对的insert
4. 数据库隔离级别:
- 读未提交:事务中的修改,即使没有提交,其他事务也可以看得到,会导致“脏读”、“幻读”和“不可重复读取”
- 读提交:保证一个事务不会读到另一个并行事务已修改但未提交的数据,避免了“脏读取”,但不能避免“幻读”和“不可重复读取”。大多数主流数据库的默认事务等级
- 可重复读:保证一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据。避免了“脏读取”和“不可重复读取”的情况,但不能避免“幻读”。带来了更多的性能损失。MySQL的默认级别
- 串行化:最严格的级别,事务串行执行,资源消耗最大。
5. 读取数据库记录的分类:
- 读取快照:直接读取的是当前记录,不管有没有事务正在准备修改记录。在读未提交,读提交,可重复读的隔离级别下,普通select使用快照读,不加锁,并发非常高;在串行化的隔离级别下,普通select会升级为
select ... in share mode
; - 等待当前所有事务提交读取最新记录:加锁的SQL语句,都是这样。
6. 数据库的锁按照作用范围分为行锁、表锁:
- 行锁:锁定记录行;
- 表锁:锁定整个表。
- 数据库能够确定哪那些行需要锁的情况下使用行锁,如果不知道会影响哪些行的时候就会使用表锁;
- 例如,SQL语句中where后面的条件判断
where name=?
,如果name这个字段加了索引,数据库明确知道会影响哪一行,它就会使用行锁;如果name没有使用索引,就会加表锁。
7. 从程序角度来讲,锁分为乐观锁和悲观锁:
乐观锁:
- 认为在读取记录到将修改记录重新写回数据库的这个事务过程中,其他人不会修改这条记录,所以不去锁定该记录;
- 乐观锁通常是额外加一个字段version,作为是否被修改的标记(也可以自己设计检查的字段,满足自己的业务需求);
- 在提交修改的时候,根据version判断一下记录有没有被修改,没有就提交修改,有就返回未修改,由写程序的人决定该怎么处理;
- 乐观锁适合读多写少的场景;
- 乐观锁并不需要数据库的锁配合。
悲观锁:
- 认为在读取记录到将修改记录重新写回数据库的这个事务过程中,其他人会修改这条记录,所以锁定该记录;
- 悲观锁使用数据库的锁来实现;
- 乐观锁适合写数据、修改数据很多的场景;
- 悲观锁是通过数据库的锁实现的。
网友评论