前言
本文总结一下数据库中基础知识:锁以及事务。
锁的分类
对数据库中数据的操作我们可以分为写和读。读时加锁吗?写时加锁吗?这两种类型的加的锁通常被称为共享锁(Shared Lock) 和排他锁(exclusive lock) 也叫读锁(read lock)和写锁(write lock)。
锁的分类:共享锁和排他锁
- 读锁是共享的,互不阻塞
- 写锁是排他的,一个写锁阻塞其他的一个写锁和读锁。防止一个用户写入时,其他用户读取错误数据。
那加锁时,对于那些范围的数据进行排他处理呢?加锁的范围越小,我们程序的并发性越高。锁也会带来,加锁,检查锁,释放锁的开销。
锁的范围分类:行锁 表锁。
- 行锁:对行进行加锁。最大程度的支持并发操作,同时也带来了最大的锁开销。行级锁只在存储引擎层实现。
- 表锁:对整张表进行加锁,开销最小的策略。
共享锁和排他锁都是悲观锁。乐观锁一般通过版本号的方式来更新:update user set name = 'xxx' where id = 1 and version = xxx
事务
事务指的是一系列的sql语句打包成事务处理,这么一组sql要么全部执行成功,要么全部执行失败。事务的实现与之前所说的共享锁和排他锁有关。
说一个经典的转账案例:在A的账户余额充足的情况下,A给B转100 元,A的账户减100 B的账户加100 这两个操作要么都成功要么都失败,不能一个成功,一个失败。
事务的特征 ACID
- A 原子性(atomicity):事务作为最小的工作单元,事务不可被分割,事务内的语句要全部执行成功要么全部执行失败进行回滚。不能只执行一部分操作。强调事务不可被分割。
- C 一致性(consistency):数据库总是从一个一致性状态到两一个一致性状态转换。一致性保证如果后半部分的因为崩溃sql没有提交,前半部分也不会被保存到数据库中。强调执行的完整性。
- I 隔离性(isolation):一个事务所作的修改在最终提交以前,对其他事务是不可见的。隔离性会分隔离级别。
- D 持久性(durability):提交之后就能永久的保存到数据库中。此时即使数据库崩溃,修改的数据也不会丢失。
事务隔离性的隔离级别
隔离级别:
-
未提交读:READ UNCOMMITED
- 事务中的修改在没提交时,对其他事务也是可见的。其他事务可以读到未提交的数据称之为脏读(Dirty Read)。
- 别人对一个数据写的时候你还能读。写操作时候对数据不锁定。
-
提交读 READ COMMITED
- 满足未提交读的缺陷,在事务的修改提交前,对其他事务是不可见的。但是一个事务在未处理完一个事务的过程中,对某个数据的读可能是不同的,因为其他的事务在这个过程中进行了修改和提交。称之为不可重复读。(在事务内读到某个数据可能是不一样的,其他的事务对数据进行了操作)对于写的数据加锁,对读的数据未加锁。
-
可重复读 REPEATABLE READ
- 可重复读解决上面两个问题,通过事务内读数据的时候也对数据进行锁定。但是没有解决当某个事务读取某个范围的记录时。另一个事务又在该范围内插入新的数据。这种现象称之为幻读。
- MYSQL 默认事务隔离级别
-
SERIALIZABLE 可串行化
- 最高的隔离级别。强制事务的串行执行。可串行化在读每行数据时都进行加锁。读的时候也加锁。
隔离级别 | 脏读 | 不可重复读 | 幻读 | 加锁读 |
---|---|---|---|---|
READ UNCOMMITED | YES | YES | YES | NO |
READ COMMITED | NO | YES | YES | NO |
REPEATABLE READ | NO | NO | YES | NO |
SERIALIZABLE | NO | NO | NO | YES |
sql 中的锁
- 无锁
- select ... from
- 共享锁
- select ... lock in share mode
- 排它锁
- update
- delete
- insert
- select ... for update
只有「明确」指定主键,才会执行锁,否则将会执行表锁
无锁:select * from user wehre id = -1 for update // id 不存在
行锁:select * from user wehre id = 2 for update
表锁:select * from user wehre name = '12' for update // 主键不明确
数据库中的锁和代码锁
数据库中加锁的应用场景:
- 粒度小,适合集群
代码中的应用锁:
- 粒度大,需要封装,可以用于分布式和集群。
行锁和表锁的算法实现
行锁:
- Record Lock(普通行锁)
- 对于键值在条件范围内,且存在的记录,使用" Record Lock ",即普通的行锁机制;
- Gap Lock(间隙锁)
- 对于键值在条件范围内但并不存在的记录,叫做" 间隙(GAP) ",InnoDB会对这个“间隙”加锁,这种锁机制就是所谓的" Gap Lock "(间隙锁);
- Next-Key Lock(行 & 间隙)
- 对于存在与不存在的数据同时加锁,则称为" Next-Key Lock ";
- Next-Key Lock包含Record Lock和Gap Lock;
表锁:
- 意向锁
- 当一个事务带着表锁去访问一个被加了行锁的资源,那么,此时,这个行锁就会升级为意向锁,将表锁住。
- 常用的意向锁有:意向共享锁,意向排它锁,共享意向排它锁
- 自增锁
- 事务插入自增类型的列时获取自增锁
- 如果一个事务正在往表中插入自增记录,所有其他事务的插入必须等待
行锁和表锁是锁粒度的概念,共享锁和排它锁是他们的具体实现。
References
- 码农翻身
- 《高性能MySQL》
- https://github.com/hongwen1993/all/blob/master/database/lock.md