事务指的是满足如下四个特性【ACID特性】的一组操作,可以通过 Commit 提交一个事务,也可以使用 Rollback 进行回滚。
在并发环境下,事务的隔离性很难保证,因此会出现很多并发一致性问题。
这里在总结归纳一下:
MySQL 中提供了两种封锁粒度:行级锁以及表级锁。
我们在使用时应该尽量只锁定需要修改的那部分数据,而不是所有的资源。锁定的数据量越少,发生锁争用的可能就越小,系统的并发程度就越高。
但是加锁需要消耗资源,锁的各种操作(包括获取锁、释放锁、以及检查锁状态)都会增加系统开销。因此封锁粒度越小,系统开销就越大。
行锁的优点:
1.当在许多线程中访问不同的行时只存在少量锁定冲突。
2.回滚时只有少量的更改
3.可以长时间锁定单一的行。
行锁的缺点:
1.比页级或表级锁定占用更多的内存。
2.当在表的大部分中使用时,比页级或表级锁定速度慢,因为你必须获取更多的锁。
3.如果你在大部分数据上经常进行GROUP BY操作或者必须经常扫描整个表,比其它锁定明显慢很多。
4.用高级别锁定,通过支持不同的类型锁定,你也可以很容易地调节应用程序,因为其锁成本小于行级锁定。
三种锁的算法:Record Lock、Gap Lock 和 Next-Key Lock
1.Record Lock
记录锁(Record Lock)是加到索引记录上的锁,假设我们存在下面的一张表 users:
CREATE TABLE users(
id INT NOT NULL AUTO_INCREMENT,
last_name VARCHAR(255) NOT NULL,
first_name VARCHAR(255),
age INT,
PRIMARY KEY(id),
KEY(last_name),
KEY(age)
);
如果我们使用 id 或者 last_name 作为 SQL 中 WHERE 语句的过滤条件,那么 InnoDB 就可以通过索引建立的 B+ 树找到行记录并添加索引,但是如果使用 first_name 作为过滤条件时,由于 InnoDB 不知道待修改的记录具体存放的位置,也无法对将要修改哪条记录提前做出判断就会锁定整个表。
2.Gap Lock
记录锁是在存储引擎中最为常见的锁,除了记录锁之外,InnoDB 中还存在间隙锁(Gap Lock),间隙锁是对索引记录中的一段连续区域的锁;当使用类似 SELECT * FROM users WHERE id BETWEEN 10 AND 20 FOR UPDATE; 的 SQL 语句时,就会阻止其他事务向表中插入 id = 15 的记录,因为整个范围都被间隙锁锁定了。
间隙锁是存储引擎对于性能和并发做出的权衡,并且只用于某些事务隔离级别。
虽然间隙锁中也分为共享锁和互斥锁,不过它们之间并不是互斥的,也就是不同的事务可以同时持有一段相同范围的共享锁和互斥锁,它唯一阻止的就是其他事务向这个范围中添加新的记录。
Gap Lock的作用是为了阻止多个事务将记录插入到同一个范围内,这样会导致幻读的产生。用户可以通过以下两种服务显式地关闭Gap Lock。
1.将事务的隔离级别设置为READ COMMITTED
2.将参数innodb_locks_unsafe_for_binlog设置为1
除了外键约束和唯一性检查需要的Gap Lock,其余情况仅使用Record Lock进行锁定。这样做破坏了事务的隔离性,并且对于replication会导致主动数据的不一致。
3.Next-Key Lock
Next-Key Lock是结合了Gap Lock和Record Lock的一种锁定算法,其设置的目的是为了解决幻读问题。
当查询的索引含有唯一属性的时候,InnoDB存储引擎会对Next-Key Lock进行优化,将其降为Record Lock,即仅仅锁住索引本身,而不是范围,从而提高并发效率。
对于唯一值的锁定,Next-Key Lock降级为Record Lock仅存在于查询所有的唯一索引列。若唯一索引由多个列组成,而查询仅是查找多个唯一索引列中的其中一个,那么查询其实是range类型,而不是point类型的查询。此时InnoDB存储引擎依然使用Next-Key Lock进行锁定。
下面我们针对大部分的SQL类型分析是如何加锁的,假设事务隔离级别为可重复读
select … from
不加任何类型的锁
select…from lock in share mode
在扫描到的任何索引记录上加共享的(shared)next-key lock,还有主键聚集索引加排它锁
select…from for update
在扫描到的任何索引记录上加排它的next-key lock,还有主键聚集索引加排它锁
update…where delete from…where
在扫描到的任何索引记录上加next-key lock,还有主键聚集索引加排它锁
insert into…
简单的insert会在insert的行对应的索引记录上加一个排它锁,这是一个record lock,并没有gap,所以并不会阻塞其他session在gap间隙里插入记录。不过在insert操作之前,还会加一种锁,官方文档称它为insertion intention gap lock,也就是意向的gap锁。这个意向gap锁的作用就是预示着当多事务并发插入相同的gap空隙时,只要插入的记录不是gap间隙中的相同位置,则无需等待其他session就可完成,这样就使得insert操作无须加真正的gap lock。想象一下,如果一个表有一个索引idx_test,表中有记录1和8,那么每个事务都可以在2和7之间插入任何记录,只会对当前插入的记录加record lock,并不会阻塞其他session插入与自己不同的记录,因为他们并没有任何冲突。
1.读写锁
他们满足下面的兼容关系:
- | X | S |
---|---|---|
X | x | x |
S | x | v |
对应的是以下两个规定:
1.一个事务对数据对象 A 加了 X 锁,就可以对 A 进行读取和更新。加锁期间其它事务不能对 A 加任何锁。
2.一个事务对数据对象 A 加了 S 锁,可以对 A 进行读取操作,但是不能进行更新操作。加锁期间其它事务能对 A 加 S 锁,但是不能加 X 锁。
2.意向锁
使用意向锁(Intention Locks)可以更容易地支持多粒度封锁。
在存在行级锁和表级锁的情况下,事务 T 想要对表 A 加 X 锁,就需要先检测是否有其它事务对表 A 或者表 A 中的任意一行加了锁,那么就需要对表 A 的每一行都检测一次,这是非常耗时的。
意向锁在原来的 X/S 锁之上引入了 IX/IS,IX/IS 都是表锁,用来表示一个事务想要在表中的某个数据行上加 X 锁或 S 锁。有以下两个规定:
通过引入意向锁,事务 T 想要对表 A 加 X 锁,只需要先检测是否有其它事务对表 A 加了 X/IX/S/IS 锁,如果加了就表示有其它事务正在使用这个表或者表中某一行的锁,因此事务 T 加 X 锁失败。
这里顺便提一下在 MySQL隐式与显示锁定:
在MySQL的 InnoDB 存储引擎采用的是两段锁协议,会根据隔离级别在需要的时候自动加锁,并且所有的锁都是在同一时刻被释放,这被称为隐式锁定。
InnoDB 也可以使用特定的语句进行显示锁定:
SELECT ... LOCK In SHARE MODE;
SELECT ... FOR UPDATE;
1.未提交读(Read Uncommitted)
名称解释:事务中的修改,即使没有提交,对其它事务也是可见的。
含义:限制同一数据写事务时禁止其他写事务。解决”更新丢失”。(一事务写时禁止其他事务写)
所需要的锁:排他写锁
2.提交读(Read Committed)
名称解释:一个事务只能读取已经提交的事务所做的修改。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。
含义:限制同一数据写事务时禁止其它读写事务。解决”脏读”,以及”更新丢失”。(一事务写时禁止其他事务读写)
所需要的锁:排他写锁,瞬间共享读锁
3.可重复读(Repeatable Read)
名称解释:保证在同一个事务中多次读取同样数据的结果是一样的。
含义:限制同一数据写事务时禁止其他读写事务,读事务时禁止其他写事务(允许读)。解决”不可重复读”,以及”更新丢失”和”脏读”。(一事务写时禁止其他事务读写、一事务读时禁止其他事务写),注意他没有解决幻读,解决幻读需要增加范围锁(range lock)或者表锁。
所需要的锁:排他写锁,共享读锁。
4.可串行化(Serializable)
名称解释:强制事务串行执行。
含义解释:限制所有读写事务都必须串行化实行。它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行。如果仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。(一事务写时禁止其他事务读写、一事务读时禁止其他事务读写)
所须的锁:范围锁或表锁
(2019.12.31更新)
数据库 | 默认隔离级别 |
---|---|
MySQL | 可重复读(Repeatable Read) |
Oracle | 提交读(Read Committed) |
SQLServer | 提交读(Read Committed) |
PostgreSQL | 提交读(Read Committed) |
在MySQL数据库中,支持上面四种隔离级别,默认的为Repeatable read (可重复读);此外,MySQL的Repeatable Read隔离级别也解决了幻读问题(通过Next-key lock加锁方法即范围锁解决不可重复读和幻读问题,如select * from t where a>10会对key为[10,infinite)范围的行加锁,这样其他事务就不能对此范围内key对应的行更改)达到了SQL、SQL2标准中的Serializable级别。
在Oracle数据库中,只支持Serializable (串行化)级别和Read committed (读已提交)这两种级别,其中默认的为Read committed级别。
在MySQL数据库中查看当前事务的隔离级别:
select @@tx_isolation;
在MySQL数据库中设置事务的隔离 级别:
set [glogal | session] transaction isolation level 隔离级别名称; //设置全部连接或当前连接的事务隔离级别
set tx_isolation=’隔离级别名称; //设置当前连接的事务隔离级别
记 A->B 表示 A 函数决定 B,也可以说 B 函数依赖于 A。
如果 {A1,A2,… ,An} 是关系的一个或多个属性的集合,该集合函数决定了关系的其它所有属性并且是最小的,那么该集合就称为键码(主键)。
对于 A->B,如果能找到 A 的真子集 A’,使得 A’-> B,那么 A->B 就是部分函数依赖,否则就是完全函数依赖。
对于 A->B,B->C,则 A->C 是一个传递函数依赖。
2. 第二范式 (2NF)
每个非主属性完全函数依赖于键码。
可以通过分解来满足。
3. 第三范式 (3NF)
每一个非主属性即不会部分依赖于键码也不传递函数依赖于键码。
4.BCNF
所有非主属性对每一个码都是完全函数依赖;
所得的主属性对每一个不包含他的码,也是完全函数依赖;
没有任何属性完全函数依赖于非码的任何一个属性;
Entity-Relationship,有三个组成部分:实体、属性、联系。
用来进行关系型数据库系统的概念设计。
关系型数据库和非关系数据库之间的区别
上述南国讲的大部分是依据关系型数据库来讲解的知识点,有关于关系型数据库和非关系型数据库的比较,南国这里推荐我认识的以为学长写的博客:如何看待当今多种多样的数据库
参看资料:
CS-Notes数据库系统原理