数据库三大范式
数据库范式是设计数据库时,需要遵循的一些规范。各种范式是条件递增的联系,越高的范式数据库冗余越小。常用的数据库三大范式为:
第一范式(1NF):每个列都不可以再拆分,强调的是列的原子性。第一范式要求数据库中的表都是二维表。
第二范式(2NF):在第一范式的基础上,一个表必须有一个主键,非主键列 完全依赖 于主键,而不能是依赖于主键的一部分。
第三范式(3NF):在第二范式的基础上,非主键列只依赖(直接依赖)于主键,不依赖于其他非主键。
触发器
触发器(trigger)是与表相关的数据库对象,是用户定义在关系表上的一类由事件驱动的特殊的存储过程,在满足定义条件时触发,并执行触发器中定义的 语句集合。触发器的这种特性可以协助应用在数据库端确保 数据库的完整性。
使用场景
可以通过数据库中的相关表实现 级联更改;
实时监控某张表中的某个字段的更改,并需要做出相应的处理。
触发器的作用举例:
1.可在写入数据表前,强制检验或转换数据。
2.触发器发生错误时,异动的结果会被撤销。
索引
索引的优点
通过创建 唯一性索引,可以保证数据库表中每一行数据的唯一性;
可以加快数据的 检索速度,这也是创建索引的主要原因;
可以加速表和表之间的连接,特别是在实现 数据的参考完整性 方面特别有意义;
通过使用索引,可以在查询的过程中,使用 优化隐藏器,提高系统性能。
索引的缺点
时间上,创建和维护索引都要耗费时间,这种时间随着数据量的增加而增加,具体地,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度;
空间上,索引需要占 物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大。
索引为什么可以加快查询速度?
- 因为使用索引后可以不用扫描全表来定位某行的数据,而是先通过索引表找到该行数据对应的物理地址然后访问相应的数据
- 同时,由于索引底层实现的有序性,使得在进行数据查询时,能够避免在磁盘不同扇区的随机寻址
- 使用索引后能够通过磁盘预读使得在磁盘上对数据的访问大致呈顺序的寻址。这本质上是依据局部性原理所实现的
总结
- 索引大大减少了服务器需要扫描的数据量
- 索引可以帮助服务器避免排序和临时表
- 索引可以将随机I/O变成顺序I/O(局部性原理)
索引的数据结构
数据库索引根据结构分类,主要有 B 树索引、Hash 索引 和 位图索引 三种。
B 树索引
- B 树索引,又称 平衡树索引,是 MySQL 数据库中使用最频繁的索引类型,MySQL、Oracle 和 SQL Server
数据库默认的都是 B 树索引(实际是用 B+ 树实现的,因为在查看表索引时,MySQL 一律打印 BTREE,所以简称为 B 树索引)。 - B 树索引以 树结构
组织,它有一个或者多个分支结点,分支结点又指向单级的叶结点。其中,分支结点用于遍历树,叶结点则保存真正的值和位置信息。 - B+ 树是在 B 树基础上的一种优化,使其更适合实现外存储索引结构。
一棵 m 阶 B-Tree 的特性如下:
- 每个结点最多 m 个子结点;
- 除了根结点和叶子结点外,每个结点最少有 m/2(向上取整)个子结点;
- 所有的叶子结点都位于同一层;
- 每个结点都包含 k 个元素(关键字),这里 m/2≤k<m,这里 m/2 向下取整;
- 每个节点中的元素(关键字)从小到大排列;
- 每个元素子左结点的值,都小于或等于该元素,右结点的值都大于或等于该元素。
数据库以 B-Tree 的数据结构存储数据的图示如下:
B+ Tree 与 B-Tree 的结构很像,但是也有自己的特性:
- 所有的非叶子结点只存储 关键字信息;
- 所有具体数据都存在叶子结点中;
- 所有的叶子结点中包含了全部元素的信息;
- 所有叶子节点之间都有一个链指针。
数据库以 B+ Tree 的数据结构存储数据的图示如下:
Hash 索引
哈希索引采用一定的 哈希算法(常见哈希算法有 直接定址法、平方取中法、折叠法、除数取余法、随机数法),将数据库字段数据转换成定长的 Hash 值,与这条数据的行指针一并存入 Hash 表的对应位置,如果发生 Hash 碰撞(两个不同关键字的 Hash 值相同),则在对应 Hash 键下以 链表形式 存储。
检索时不需要类似 B+ 树那样从根节点到叶子节点逐级查找,只需一次哈希算法即可立刻定位到相应的位置,速度非常快,平均检索时间为 O(1)。
位图索引
B 树索引擅长于处理包含许多不同值的列,但是在处理基数较小的列时会变得很难使用。如果用户查询的列的基数非常的小, 即只有几个固定值,如性别、婚姻状况、行政区等等,要么不使用索引,查询时一行行扫描所有记录,要么考虑建立位图索引。
位图索引为存储在某列中的每个值生成一个位图。例如针对表中婚姻状况这一列,生成的位图索引大致如下所示:
Value / Row ID 1 2 3 4 5 6 ……
未婚 1 1 0 0 0 0
已婚 0 0 0 1 1 1
离婚 0 0 1 0 0 0
对于婚姻状况这一列,索引包含 3 个位图,即生成有 3 个向量,分别属于每一个取值,每个位图为每一个人(行)都分配了 0/1 值(每一行有且仅有一个 1 ),未婚为 110000……,已婚为 000111……,离婚为 001000……。
当进行数据查找时,只要查找相关位图中的所有 1 值即可(可根据查询需求进行与、或运算)。
例如, Oracle 用户可以通过为 create index 语句简单地添加关键词 bitmap 生成位图:
CREATE BITMAP INDEX acc_marital_idx ON account (marital_cd);
除了上述提及的,位图索引适合只有几个固定值的列,还需注意 ,位图索引适合静态数据,而不适合索引频繁更新的列。
B+树的好处
由于 B+ 树的内部结点只存放键,不存放值,因此,一次读取,可以在同一内存页中获取更多的键,有利于更快地缩小查找范围。
B+ 树的叶结点由一条链相连,因此当需要进行一次 全数据遍历 的时候,B+ 树只需要使用 O(logN) 时间找到最小结点,然后通过链进行 O(N) 的顺序遍历即可;或者,在找 大于某个关键字或者小于某个关键字的数据 的时候,B+ 树只需要找到该关键字然后沿着链表遍历即可。
还有一个优点,因为数据都在叶子节点上,B+树查找时稳定为O(log n)
Hash 索引和 B+ 树索引的区别
Hash 索引和 B+ 树索引有以下几点显见的区别:
- Hash 索引进行等值查询更快(一般情况下),但是却无法进行范围查询;
- Hash 索引不支持使用索引进行排序;
- Hash 索引不支持模糊查询以及多列索引的最左前缀匹配,原理也是因为 Hash 函数的不可预测;
- Hash 索引任何时候都避免不了回表查询数据,而 B+ 树在符合某些条件(聚簇索引,覆盖索引等)的时候可以只通过索引完成查询;
- Hash 索引虽然在等值查询上较快,但是不稳定,性能不可预测,当某个键值存在大量重复的时候,发生 Hash 碰撞,此时效率可能极差;而
B+ 树的查询效率比较稳定,对于所有的查询都是从根结点到叶子结点,且树的高度较低。
前缀索引
有时需要索引很长的字符列,它会使索引变大并且变慢,一个策略就是索引开始的几个字符,而不是全部值,即被称为 前缀索引,以节约空间并得到好的性能。使用前缀索引的前提是 此前缀的标识度高,比如密码就适合建立前缀索引,因为密码几乎各不相同。
前缀索引需要的空间变小,但也会降低选择性。索引选择性(INDEX SELECTIVITY)是不重复的索引值(也叫基数)和表中所有行数(T)的比值,数值范围为 1/T ~1。高选择性的索引有好处,因为在查找匹配的时候可以过滤掉更多的行,唯一索引的选择率为 1,为最佳值。对于前缀索引而言,前缀越长往往会得到好的选择性,但是短的前缀会节约空间,所以实操的难度在于前缀截取长度的抉择,可以通过调试查看不同前缀长度的 平均匹配度,来选择截取长度。
最左前缀匹配原则
在 MySQL 建立 联合索引(多列索引) 时会遵守最左前缀匹配原则,即 最左优先,在检索数据时从联合索引的最左边开始匹配。例如有一个 3 列索引(a,b,c),则已经对(a)、(a,b)、(a,b,c)上建立了索引。所以在创建 多列索引时,要根据业务需求,where 子句中 使用最频繁 的一列放在最左边。
根据最左前缀匹配原则,MySQL 会一直向右匹配直到遇到 范围查询(>、<、between、like)就停止匹配,比如采用查询条件 where a = 1 and b = 2 and c > 3 and d = 4 时,如果建立(a,b,c,d)顺序的索引,d 是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,并且 where 子句中 a、b、d 的顺序可以任意调整。
如果建立的索引顺序是 (a,b) ,那么根据最左前缀匹配原则,直接采用查询条件 where b = 1 是无法利用到索引的。
添加索引的原则
索引虽好,但也不是无限制使用的,以下为添加索引时需要遵循的几项建议性原则:
- 在 查询中很少使用 或者参考的列不要创建索引。由于这些列很少使用到,增加索引反而会降低系统的维护速度和增大空间需求。
- 只有很少数据值的列
也不应该增加索引。由于这些列的取值很少,区分度太低,例如人事表中的性别,在查询时,需要在表中搜索的数据行的比例很大。增加索引,并不能明显加快检索速度。 - 定义为 text、image 和 bit 数据类型的列不应该增加索引。这是因为,这些列的数据量要么相当大,要么取值很少。
- 当 修改性能远远大于检索性能 时,不应该创建索引。这时因为,二者是相互矛盾的,当增加索引时,会提高检索性能,但是会降低修改性能。 定义有
外键 的数据列一定要创建索引。
聚簇索引
聚簇索引,又称 聚集索引, 首先并不是一种索引类型,而是一种数据存储方式。具体的,聚簇索引指将 数据存储 和 索引 放到一起,找到索引也就找到了数据。
MySQL 里只有 INNODB 表支持聚簇索引,INNODB 表数据本身就是聚簇索引,非叶子节点按照主键顺序存放,叶子节点存放主键以及对应的行记录。所以对 INNODB 表进行全表顺序扫描会非常快。
特点
因为索引和数据存放在一起,所以具有更高的检索效率;
相比于非聚簇索引,聚簇索引可以减少磁盘的 IO 次数;
表的物理存储依据聚簇索引的结构,所以一个数据表只能有一个聚簇索引,但可以拥有多个非聚簇索引;
一般而言,会在频繁使用、排序的字段上创建聚簇索引。
非聚簇索引
除了聚簇索引以外的其他索引,均称之为非聚簇索引。非聚簇索引也是 B 树结构,与聚簇索引的存储结构不同之处在于,非聚簇索引中不存储真正的数据行,只包含一个指向数据行的指针。
就简单的 SQL 查询来看,分为 SELECT 和 WHERE 两个部分,索引的创建也是以此为根据的,分为 复合索引 和 覆盖索引。
事务管理
数据库的 事务(Transaction)是一种机制、一个操作序列,包含了一组数据库操作命令,其执行的结果必须使数据库从一种一致性状态变到另一种一致性状态。事务把所有的命令作为一个整体一起向系统提交或撤销操作请求,即这一组数据库命令要么都执行,要么都不执行,因此事务是一个不可分割的工作逻辑单元。如果任意一个操作失败,那么整组操作即为失败,会回到操作前状态或者是上一个节点。
因此,事务是保持 逻辑数据一致性 和 可恢复性 的重要利器。而锁是实现事务的关键,可以保证事务的完整性和并发性
事务状态
事务在其整个生命周期中会经历不同的状态,这些状态也称为 事务状态。
- 活跃状态:事务的第一个状态,任何正在执行的事务都处于此状态,所做的 更改 存储在 主内存的缓冲区 中。
- 部分提交状态:执行上次操作后,事务进入部分提交状态。之所以是部分提交,是因为所做的更改仍然在主内存的缓冲区中。
- 失败状态:如果某个检查在活动状态下失败,在活动状态或部分提交状态发生一些错误,并且事务无法进一步执行,则事务进入失败状态。
- 中止状态:如果任何事务已达到失败状态,则恢复管理器将数据库回滚到开始执行的原始状态。
- 提交状态:如果所有操作成功执行,则来自 部分提交状态 的事务进入提交状态。无法从此状态回滚,它是一个新的 一致状态。
四大特性
事务具有 4 个特性,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability),这 4 个特性通常简称为 ACID,关系型数据库 需要遵循 ACID 规则。
原子性
事务是最小的执行单位,不可分割的(原子的)。事务的原子性确保动作要么全部执行,要么全部不执行。
以 银行转账 事务为例,如果该事务提交了,则这两个账户的数据将会更新;如果由于某种原因,事务在成功更新这两个账户之前终止了,则不会更新这两个账户的余额,并且会 撤销 对任何账户余额的修改,回到此操作前状态,即事务不能部分提交。
一致性
当事务完成时,数据必须处于一致状态,多个事务对同一个数据读取的结果是相同的。
以银行转账事务事务为例。在事务开始之前,所有 账户余额的总额处于一致状态。在事务进行的过程中,一个账户余额减少了,而另一个账户余额尚未修改。因此,所有账户余额的总额处于不一致状态。但是当事务完成以后,账户余额的总额再次恢复到一致状态。
隔离性
并发访问数据库 时,一个用户的事务不被其他事务所干扰,各个事务不干涉内部的数据。
修改数据的事务可以在另一个使用相同数据的事务开始之前访问这些数据,或者在另一个使用相同数据的事务结束之后访问这些数据。
持久性
一个事务被提交之后,它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
如何实现四大特性
事务的 ACID 特性是由关系数据库管理系统来实现的。
DBMS 采用 日志 来保证事务的 原子性、一致性 和 持久性。日志记录了事务对数据库所做的更新,如果某个事务在执行过程中发生错误,就可以根据日志,撤销事务对数据库已做的更新,使数据库退回到执行事务前的初始状态。
DBMS 采用 锁机制 来实现事务的隔离性。当多个事务同时更新数据库中相同的数据时,只允许 持有锁的事务 能更新该数据,其他事务必须等待,直到前一个事务释放了锁,其他事务才有机会更新该数据。
主要靠三大日志来实现
归档日志 bin log
回滚日志 undo log
重做日志 redo log
原子性:
undo log日志记录sql操作,当发生回滚时,逆序执行逆操作来完成回滚
持久性:
bin log & redo log日志,
redo log: 为了避免每次读取数据都进行磁盘IO,MySQL的InnoDB引擎采用缓存buffer方式
redo log分为两个部分:缓冲区的redolog buffer和磁盘上的redologfile
磁盘上的redo log不受宕机影响,在每次真正执行DML操作之前先更新redo log(称为预写式日志),也就是WAL技术
WAL技术(Write-Ahead Logging):在真正把数据写入到磁盘前,先记录日志
redo log(undo log跟redo log的机制一样)都经历一次写,一次刷
写过程将引擎用户层buffer记录的数据写入核心层buffer
刷过程将核心层buffer数据真正写入磁盘
由此分为三种写刷时机,同样由参数控制:延迟写 / 实时写,实时刷 / 实时写,延迟刷
bin log有类似的刷盘时机机制,同样由参数控制:不强制要求 / 每次提交都刷盘(默认) / 集齐N次提交后刷盘
由于redo log采用循环写的方式记录下最近的操作,但要用到更久以前的操作来完成恢复时,就需要另一种日志对更久之前的操作归档
这个日志就是bin log, 两者要配合使用才能保证当数据库发生宕机重启时,数据不会丢失
MySQL提交时,分为两阶段提交,第一阶段提交时,执行器先写redo log(prepare),再写bin log,第二阶段提交时正式更新redo log(commit),保证两个日志数据的安全与同步
bin log与redo log区别:
redo log循环写,大小固定,会丢失;bin log追加写,大小通过配置参数决定,追加写到超过文件大小后会将后续日志记录到新的bin log不丢失
redo log 适用于崩溃恢复,只有redo log是crush-safe的;binlog 除了配合崩溃恢复保证持久性之外,还适用于主从复制(也是重点)
bin log所有引擎都支持,server层实现,redo log仅InnoDB支持,引擎层实现
bin log支持三种记录方式(Statement / Row / Mixed),日志类型为逻辑日志;
redo log日志类型为物理日志(物理日志用于恢复速度会快很多)
一致性:
bin log 也有类似的缓冲区,binlog刷盘时机同样是靠配置参数来控制
可以选择不实时更新,牺牲一定的一致性来换取更好的性能,默认每次提交都刷盘(保证一致性)
隔离性:
事务隔离级别: 防脏读 防不可重复读 防幻读
read uncommitted X X X
read committed √ X X
repeatable read √ √ X
serializable √ √ √
脏读:读未提交
不可重复读:同一事务两次读的结果不同(针对update)
幻读:前后多次读取,数据总量不一致(针对insert、delete)
MVCC:多版本并发控制,不同事务的读–写、写–读操作并发执行,从而提升系统性能
读未提交:就全读最新的就是读未提交,不加任何判断
读已提交和可重复读的实现:
核心处理逻辑就是判断所有版本中哪个版本是当前事务可见的,称为 ReadView
这个过程通过事务IDtrx_id来判断,这个ID严格递增
读已提交:仅判断trx_id,在本次事务之前的都有效修改,可查
可重复读:利用undo log,配合ReadView,当该事务执行相同读操作的时候,依据undo log恢复到开始时的数据,保证永远是第一次查询时的结果
串行化的实现:锁实现串行化,牺牲并发,保证ACID
注意:以上三大日志都仅记录写入性操作,不包括查询操作(因为查询不更改数据库,也就没必要记录了)
事务之间的相互影响
脏读(Dirty Read)
一个事务读取了另一个事务未提交的数据。
不可重复读(Non-repeatable Read)
就是在一个事务范围内,两次相同的查询会返回两个不同的数据,这是因为在此间隔内有其他事务对数据进行了修改。
幻读(Phantom Read)
幻读是指当事务 不是独立执行时 发生的一种现象,例如有一个事务对表中的数据进行了修改,这种修改涉及到表中的全部数据行,同时,第一个事务也修改这个表中的数据,这种修改是向表中 插入一行新数据。那么,第一个事务的用户发现表中还有没有修改的数据行,就好像发生了幻觉一样。
丢失更新(Lost Update)
两个事务同时读取同一条记录,事务 A 先修改记录,事务 B 也修改记录(B 是不知道 A 修改过),当 B 提交数据后, 其修改结果覆盖了 A 的修改结果,导致事务 A 更新丢失。
举例:
事务A和事务B操作同一个银行账号,初始余额为0。
脏读:
事务B存入100元,此时事务A查余额,为100,但事务B发生了错误,回滚了,余额又重新变成了0,出现了不一致的情况,此时便说事务A读到了脏数据。
不可重复读:
事务A查余额,为0,事务B存入100元并成功提交,此时事务A再查余额,变成了100,同样的查询得到不同的结果,这就是不可重复读。
幻读:
与不可重复读有点像,但幻读强调的是查询结果为多条数据的场景。A查余额明细,有20条,B存入100元,使得余额明细多了一条,然后提交成功,此时A再查余额明细,得到了21条,这就是幻读。
丢失更新:
事务A和B同时开始,A存入100元,B存入50元,A先于B提交成功,此时余额为100,但当B提交成功后,余额变成了50,A对余额的更新就不见了,这就是丢失更新。
事务的隔离级别
为了尽可能的避免上述事务之间的相互影响,从而达到事务的四大特性,SQL 标准定义了 4 种不同的事务隔离级别(TRANSACTION ISOLATION LEVEL),即 并发事务对同一资源的读取深度层次
- 读取未提交
最低的隔离级别,一个事务可以读到另一个事务未提交的结果,所有的并发事务问题都会发生。
- 读取已提交
只有在事务提交后,其更新结果才会被其他事务看见,可以解决 脏读问题,但是不可重复读或幻读仍有可能发生。Oracle 默认采用的是该隔离级别。
- 可重复读
在一个事务中,对于同一份数据的读取结果总是相同的,无论是否有其他事务对这份数据进行操作,以及这个事务是否提交,除非数据是被本身事务自己所修改。可以解决 脏读、不可重复读。MySQL 默认采用可重复读隔离级别。
- 可串行化
事务 串行化执行,隔离级别最高,完全服从 ACID,牺牲了系统的并发性,也就是说,所有事务依次逐个执行,所以可以解决并发事务的所有问题。
脏读:一个事务读取到另一个事务还未提交的数据。
不可重复读:在一个事务中多次读取同一个数据时,结果出现不一致。
幻读:在一个事务中使用相同的 SQL 两次读取,第二次读取到了其他事务新插入的行。
【读已提交和可重复读】MVCC原理
版本链、ReadView(读视图,从代码层面其实是一个结构体,主要做事务可见性判断)
版本链:
在InnoDB引擎中,每行记录的后面会保存两个隐藏的列:trx_id、roll_pointer。
trx_id: 用于保存每次对该记录进行修改的事务的id。
roll_pointer: 存储一个指针,指向这条记录上一个版本的地址,获取到该记录上一个版本的数据信息。
ReadView:
读已提交和可重复读的区别就在于它们生成的ReadView的策略不同.
读已提交每次查询时都会生成一个新的ReadView
而可重复读每次查询都复用第一次生成的ReadView
然后分别按照ReadView的访问规则最终实现读已提交和可重复读。
锁
锁的分类
从数据库系统的角度,锁模式可分为以下6 种类型:
-
共享锁(S):又叫他读锁。可以并发读取数据,但不能修改数据。也就是说当数据资源上存在共享锁时,所有的事务都不能对该数据进行修改,直到数据读取完成,共享锁释放。
-
排它锁(X):又叫独占锁、写锁。对数据资源进行增删改操作时,不允许其它事务操作这块资源,直到排它锁被释放,从而防止同时对同一资源进行多重操作。
-
更新锁(U):防止出现死锁的锁模式,两个事务对一个数据资源进行先读取再修改的情况下,使用共享锁和排它锁有时会出现死锁现象,而使用更新锁就可以避免死锁的出现。
资源的更新锁一次只能分配给一个事务,如果需要对资源进行修改,更新锁会变成排它锁,否则变为共享锁。 -
意向锁:表示 SQL Server 需要在 层次结构中的某些底层资源上 获取共享锁或排它锁。例如,放置在 表级 的 共享意向锁表示事务打算在表中的页或行上放置共享锁。在表级设置意向锁可防止另一个事务随后在包含那一页的表上获取排它锁。
意向锁可以提高性能,因为 SQL Server 仅在 表级
检查意向锁来确定事务是否可以安全地获取该表上的锁,而无须检查表中的每行或每页上的锁以确定事务是否可以锁定整个表。
意向锁包括意向共享 (IS)、意向排它 (IX) 以及与意向排它共享 (SIX)。 -
架构锁:在执行 依赖于表架构的操作 时使用。架构锁的类型为:架构修改 (Sch-M) 和架构稳定性 (Sch-S),执行表的数据定义语言(DDL)操作(例如添加列或除去表)时使用架构修改锁,当编译查询时,使用架构稳定性锁。
-
大容量更新锁(BU):向表中大容量复制数据并指定了 TABLOCK 提示时使用。大容量更新锁允许进程将数据并发地大容量复制到同一表,同时防止其它不进行大容量复制数据的进程访问该表。
事务隔离级别与锁的关系
- 在 读取未提交 隔离级别下,读取数据不需要加 共享锁,这样就不会跟被修改的数据上的 排他锁 冲突;
- 在 读取已提交 隔离级别下,读操作需要加 共享锁,但是在语句执行完以后释放共享锁;
- 在 可重复读 隔离级别下,读操作需要加 共享锁,但是在事务提交之前并不释放共享锁,也就是必须等待事务执行完毕以后才释放共享锁;
- 可串行化 是限制性最强的隔离级别,因为该级别 锁定整个范围的键,并一直持有锁,直到事务完成。
死锁
死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方的资源,从而导致恶性循环的现象。
常见的解决死锁的方法
- 如果不同程序并发存取多个表,尽量约定 以相同的顺序访问表,可以大大降低死锁机会;
- 在同一个事务中,尽可能做到 一次锁定所需要的所有资源,减少死锁产生概率;
- 对于非常容易产生死锁的业务部分,可以尝试使用 升级锁定颗粒度,通过 表级锁 定来减少死锁产生的概率。
乐观锁和悲观锁
DBMS 中的 并发控制 的任务是确保在 多个事务同时存取数据库中同一数据 时不破坏事务的隔离性和统一性以及数据库的统一性。乐观并发控制(乐观锁)和悲观并发控制(悲观锁)是并发控制主要采用的技术手段。
悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。在查询完数据的时候就把事务锁起来,直到提交事务。这对于长事务来讲,可能会严重影响系统的并发处理能力。实现方式:使用数据库中的锁机制。
乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。乐观锁适用于 读多写少的应用场景,这样可以提高吞吐量。实现方式:一般会使用版本号机制或 CAS 算法实现。
乐观锁虽然“乐观”,但也有他的保护措施,判断是否有其他线程对数据进行修改。
两种判断方式:
- 版本号,数据每次更新,版本号都会更新,记下初始时的版本号,要修改前查看版本号,跟之前的不一样就说明有其他线程修改了该数据,本线程不再更新,更新失败
- CAS算法,思路是用三个变量:需要读写的内存位置V、进行比较的预期原值A、拟写入的新值B,
如果位置V的值仍为预期的A时说明没有其他线程改动它,可以修改为新值B,否则不做修改
此处评论已关闭