基础
1.MySQL中varchar与char的区别?
CHAR和VARCHAR最大的不同就是一个是固定长度,一个是可变长度。
CHAR存储定长数据很方便,CHAR字段上的索引效率级高,比如定义 char(10),那么不论你存储的数据是否达到了10个字节,都要占去10个字节的空间,不足的自动用空格填充(并且如果存储的char类型的字符串后面有空格的话,innodb会忽略)。
VARCHAR值保存时只保存需要的字符数,另加一个字节来记录长度(如果列声明的长度超过255,则 使用两个字节)。
一般来说,CHAR比VARCHAR更快,因为CHAR是固定长度的,而VARCHAR需要增加一个长度标识,处理时需要多一次运算。但是有例外,参考:MySQL Innodb数据库性能实践——VARCHAR vs CHAR,但是如果使用的是Innodb引擎的话,推荐使用varchar代替char。特别是字符串的平均长度比最大长度要小很多的情况;当然,如果你的字符串本来就很短或者字符串长度固定,例如只有10个字符,那么就优先选CHAR了。
2.MySQL中int(11)与int(3)的区别?
当我们在选择使用int的类型的时候,不论是int(3)还是int(11),它在数据库里面存储的都是4个字节的长度。
如果int的值为10
int(11)显示结果为00000000010
int(3)显示结果为010
就是显示的长度不一样而已,但都是占用四个字节的空间,所以存储的数据不会因为超过3位而不能存储。
类型长度,参考:MySQL数据类型。
更多知识点,参考高性能Mysql——Schema与数据类型优化。
3.date,datetime和timestamp数据类型有什么区别?
-
格式
一个完整的datetime日期格式如下:YYYY-MM-DD HH:MM:SS[.fraction],它可分为两部分:date部分和time部分,其中,date部分对应格式中的“YYYY-MM-DD”,time部分对应格式中的“HH:MM:SS[.fraction]”。对于DATE字段来说,它只支持date部分,占用3个字节,对于TIME字段来说,只支持time部分,占用3个字节。
timestamp格式: YYYYMMDD HHMMSS,占用4字节 -
timestamp和datetime的不同点:
DATETIME | TIMESTAMP | |
---|---|---|
时间范围 | 1001——9999 年 | 1970——2038 年 |
时区 | 存储时间与时区无关 | 存储时间与时区有关,显示的值也依赖于时区 |
默认值 | null | 默认不为空(not null),默认值为当前时间(CURRENT_TIMESTAMP) |
4.union 与union all的区别
union 在进行表链接后会筛选掉重复的记录,所以在表链接后会对所产生的结果集进行排序运算,删除重复的记录再返回结果。 union all 则会显示重复结果,只是简单的两个结果合并并返回.所以效率比union高,在保证没有重复数据的情况下用union all.
参考:MySQL UNION操作符
5.各种join的区别?
6.drop,delete与truncate的区别?
- DROP语句:
直接删掉表。drop语句将表所占用的空间全释放掉。 - TRUNCATE语句:
删除表中数据,再插入时自增长id又从1开始 。当表被TRUNCATE 后,这个表和索引所占用的空间会恢复到初始大小。 并且不会把单独的删除操作记录记入日志保存,删除行是不能恢复的。 - DELETE语句:
删除表中数据,可以加where字句,并且同时将该行的删除操作作为事务记录在日志中保存以便进行进行回滚操作。DELETE操作不会减少表或索引所占用的空间。
delete语句为DML(data maintain Language),这个操作会被放到 rollback segment中,事务提交后才生效。如果有相应的 tigger,执行的时候将被触发。
truncate、drop是DLL(data define language),操作立即生效,原数据不放到 rollback segment中,不能回滚。
7.MySQL如何获取当前日期?*
SELECT CURRENT_DATE();
8.视图的作用,视图可以更改么?*
视图是虚拟的表,与包含数据的表不一样,视图只包含使用时动态检索数据的查询;不包含任何列或数据。使用视图可以简化复杂的sql操作,隐藏具体的细节,保护数据;视图创建后,可以使用与表相同的方式利用它们。
视图不能被索引,也不能有关联的触发器或默认值,如果视图本身内有order by 则对视图再次order by将被覆盖。
创建视图:create view XXX as XXXXXXXXXXXXXX;
对于某些视图比如未使用联结子查询分组聚集函数Distinct Union等,是可以对其更新的,对视图的更新将对基表进行更新;但是视图主要用于简化检索,保护数据,并不用于更新,而且大部分视图都不可以更新。
9.简单解释数据库三范式?数据库五种约束?
实例解析参考
第一范式就是数据库表的每一列都是不可分割的基本数据项,同一列中不能有多个值。
第二范式要求实体的属性完全依赖于主关键字(即不存在部分依赖)。所谓完全依赖是指不能存在仅依赖主关键字一部分的属性,如果存在,那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体
第三范式要求一个数据库表中不包含已在其它表中已包含的非主关键字信息(即消除传递依赖)。
例如,存在一个部门信息表,其中每个部门有部门编号(dept_id)、部门名称、部门简介等信息。那么在员工信息表中列出部门编号后就不能再将部门名称、部门简介等与部门有关的信息再加入员工信息表中。
更多知识点,参考:高性能Mysql——范式与反范式。
1.primary KEY:设置主键约束;
2.UNIQUE:设置唯一性约束,不能有重复值;
3.DEFAULT 默认值约束,height DOUBLE(3,2)DEFAULT 1.2 height不输入是默认为1,2
4.NOT NULL:设置非空约束,该字段不能为空;
5.FOREIGN key :设置外键约束。
10.不满足数据库三范式可能会出现什么情况?
数据冗余(想修改一个属性,就要更新多行数据)
插入异常(想要插入数据,结构因为表设计的问题,导致不能成功插入)
删除异常(只想删除其中的某些数据 ,结果把不该删的也删了)
更新异常(想更新一条数据,结果工作量大,还容易出错)
相关原理
11.sql 语句在 MySQL 中的执行流程
参考:高性能Mysql——一条SQL语句在Mysql中是如何执行的?
12.sql执行慢的原因有哪些,如何进行sql优化?
一、导致SQL执行慢的原因
1、硬件问题。如网络速度慢,内存不足,I/O吞吐量小,磁盘空间满了等。
2、没有索引或者索引失效。(一般在互联网公司,DBA会在半夜把表锁了,重新建立一遍索引,因为当你删除某个数据的时候,索引的树结构就不完整了。所以互联网公司的数据做的是假删除,一是为了做数据分析,二是为了不破坏索引 )
3、查找的数据过多或者需要返回的数据量过大
二、分析原因时,一定要找切入点
1、先观察,开启慢查询日志,设置相应的阈值(比如超过3秒就是慢SQL),在生产环境跑上个一天过后,看看哪些SQL比较慢。
2、Explain和慢SQL分析。比如SQL语句写的烂,索引没有或失效,关联查询太多(有时候是设计缺陷或者不得以的需求)等等。
3、Show Profile是比Explain更近一步的执行细节,可以查询到执行每一个SQL都干了什么事,这些事分别花了多少秒。
4、找DBA或者运维对MySQL进行服务器的参数调优。
解析:
(1)explain出来的各种item的意义
id:每个被独立执行的操作的标志,表示对象被操作的顺序。一般来说, id 值大,先被执行;如果 id 值相同,则顺序从上到下。
select_type:查询中每个 select 子句的类型。
table:名字,被操作的对象名称,通常的表名(或者别名),但是也有其他格式。
partitions:匹配的分区信息。
type:join 类型。
possible_keys:列出可能会用到的索引。
key:实际用到的索引。
key_len:用到的索引键的平均长度,单位为字节。
ref:表示本行被操作的对象的参照对象,可能是一个常量用 const 表示,也可能是其他表的
key 指向的对象,比如说驱动表的连接列。
rows:估计每次需要扫描的行数。
filtered:rows*filtered/100 表示该步骤最后得到的行数(估计值)。
extra:重要的补充信息。
(2)profile的意义以及使用场景
Profile 用来分析 sql 性能的消耗分布情况。当用 explain 无法解决慢 SQL 的时候,需要用profile 来对 sql 进行更细致的分析,找出 sql 所花的时间大部分消耗在哪个部分,确认 sql的性能瓶颈。
(3)explain 中的索引问题
Explain 结果中,一般来说,要看到尽量用 index(type 为 const、 ref 等, key 列有值),避免使用全表扫描(type 显式为 ALL)。比如说有 where 条件且选择性不错的列,需要建立索引。
被驱动表的连接列,也需要建立索引。被驱动表的连接列也可能会跟 where 条件列一起建立联合索引。当有排序或者 group by 的需求时,也可以考虑建立索引来达到直接排序和汇总的需求。
13.简述Mysql Innodb引擎和MyIASM引擎的区别?什么时候选择MyIASM?*
两者的对比:
- 是否支持行级锁 : MyISAM 只有表级锁(table-level locking),而InnoDB 支持行级锁(row-level locking)和表级锁,默认为行级锁。
- 是否支持外键: MyISAM不支持,而InnoDB支持。
- 是否支持事务和崩溃后的安全恢复: MyISAM 强调的是性能,每次查询具有原子性,其执行速度比InnoDB类型更快,但是不提供事务支持。但是InnoDB 提供事务支持事务,外部键等高级数据库功能。 具有事务(commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transaction-safe (ACID compliant))型表。
redo log提供崩溃后的安全恢复
- 是否支持MVCC :仅 InnoDB 支持。应对高并发事务, MVCC比单纯的加锁更高效;MVCC只在 READ COMMITTED 和 REPEATABLE READ 两个隔离级别下工作;MVCC可以使用 乐观(optimistic)锁 和 悲观(pessimistic)锁来实现;各数据库中MVCC实现并不统一。
- MyISAM读性能要优于InnoDB,除了针对索引的update操作,MyISAM的写性能可能低于InnoDB,其他操作MyISAM的写性能也是优于InnoDB的,而且可以通过分库分表来提高MyISAM写操作的速度
- MyISAM的索引和数据是分开的,而且索引是压缩的,而InnoDB的索引和数据是紧密捆绑的,没有使用压缩,所以InnoDB的体积比MyISAM庞大
- MyISAM引擎索引结构的叶子节点的数据域,存放的并不是实际的数据记录,而是数据记录的地址。索引文件与数据文件分离,这样的索引称为“非聚簇索引”。其检索算法:先按照B+Tree的检索算法检索,找到指定关键字,则取出对应数据域的值,作为地址取出数据记录。
InnoDB引擎索引结构的叶子节点的数据域,存放的就是实际的数据记录。这样的索引被称为“聚簇索引”,一个表只能有一个聚簇索引。
不要轻易相信“MyISAM比InnoDB快”之类的经验之谈,这个结论往往不是绝对的。在很多我们已知场景中,InnoDB的速度都可以让MyISAM望尘莫及,尤其是用到了聚簇索引,或者需要访问的数据都可以放入内存的应用。
选择哪种搜索引擎,应视具体应用而定
①如果是读多写少的项目, 可以考虑使用MyISAM,MYISAM索引和数据是分开的,而且其索引是压缩的,可以更好地利用内存。所以它的查询性能明显优于INNODB。压缩后的索引也能节约一些磁盘空间。MYISAM拥有全文索引的功能,这可以极大地优化LIKE查询的效率。
②如果你的应用程序一定要使用事务,毫无疑问你要选择INNODB引擎
③如果是用MyISAM的话,merge引擎可以大大加快应用部门的开发速度,他们只要对这个merge表做一些select count(*)操作,非常适合大项目总量约几亿的rows某一类型(如日志,调查统计)的业务表。
14.Mysql驱动程序是什么?*
驱动程序主要帮助编程语言与 MySQL 服务端进行通信,如果连接、关闭、传输指令与数据等
15.Innodb引擎有什么特性?*
- 插入缓冲(insert buffer)
- 二次写(double write)
- 自适应哈希索引(ahi)
- 预读(read ahead)
- 插入缓冲(insert buffer)解释:
对于聚簇索引,当执行插入操作时,id列会自动增长,页中行记录按id顺序存放,不需要随机读取其它页的数据。因此,在这样的情况下,插入操作效率很高。
对于非聚簇索引,可能叶子节点的插入不再有序,这时就需要离散访问非聚集索引页,插入性能变低。
Innodb是怎么解决这个性能变低的情况呢?也就是采用插入缓冲。
对于非聚集类索引的插入和更新操作,不是每一次都直接插入到索引页中,而是先插入到内存中。具体做法是:如果该索引页在缓冲池中,直接插入;否则,先将其放入插入缓冲区中,再以一定的频率和索引页合并,这时,就可以将同一个索引页中的多个插入合并到一个IO操作中,大大提高写性能。
插入缓冲的启用需要满足一下两个条件:
1)索引是非聚簇索引
2)索引不适合唯一的 。
如果辅助索引是唯一的,就不能使用该技术,原因很简单,因为如果这样做,整个索引数据被切分为2部分,无法保证唯一性。
- 二次写(double write)的解释:
想象这么一个场景,当数据库正在从内存向磁盘写一个数据页时,数据库宕机,从而导致这个页只写了部分数据,这就是部分写失效,它会导致数据丢失。这时是无法通过redo log恢复的,因为重做日志记录的是对页的物理修改,如果页本身已经损坏,重做日志也无能为力。
从上面分析我们知道,在部分写失效的情况下,我们在应用重做日志之前,需要原始页的一个副本,两次写就是为了解决这个问题。
其原理是这样的:
1)当刷新缓冲池脏页时,并不直接写到数据文件中,而是先拷贝至内存中的两次写缓冲区。
2)接着从两次写缓冲区分两次写入磁盘共享表空间中,每次写入1MB
3)待第2步完成后,再将两次写缓冲区写入数据文件
下面是它的原理图。
这样就可以解决上文提到的部分写失效的问题,因为在磁盘共享表空间中已有数据页副本拷贝,如果数据库在页写入数据文件的过程中宕机,在实例恢复时,可以从共享表空间中找到该页副本,将其拷贝覆盖原有的数据页,再应用重做日志即可。
- 自适应哈希索引(ahi)
哈希索引是一种非常快的等值查找方法(注意:必须是等值,哈希索引对非等值查找方法无能为力),它查找的时间复杂度为常量,InnoDB采用自适用哈希索引技术,它会实时监控表上索引的使用情况,如果认为建立哈希索引可以提高查询效率,则自动在内存中的“自适应哈希索引缓冲区”建立哈希索引。
之所以该技术称为“自适应”是因为完全由InnoDB自己决定,不需要DBA人为干预。它是通过缓冲池中的B+树构造而来,且不需要对整个表建立哈希索引,因此它的数据非常快。
- 预读
参考:https://www.cnblogs.com/geaozhang/p/7397699.html
优化
16.MySQL有哪几种索引?
- 从数据结构角度:
MySQL索引使用的数据结构主要有B+Tree索引 和 哈希索引 。
对于哈希索引来说,底层的数据结构就是哈希表,如果哈希冲突很多的话,一些索引维护操作的代价也会很高。
- 从物理存储角度:
聚集索引是基于B+树的,非叶节点只做根据主键的索引,而由叶节点来保存主键记录的数据或者指针,这样使得B+树每个非叶子节点所能保存的关键字大大增加,使得B+树层级更少,IO操作也更少。B+树叶子节点的关键字从小到大有序排列,天然具备排序功能,也将随机I/O转换为顺序I/O;左边的数据会有一个向右的指针,使得全节点遍历更快。
如果没有主键被定义,那么该表的第一个唯一非空索引被作为聚集索引。
如果没有主键也没有合适的唯一索引,那么innodb内部会生成一个隐藏的主键作为聚集索引。
非聚集索引和聚集索引差不多,通过聚集索引可以查到需要查找的数据, 而通过非聚集索引可以查到记录对应的主键值 , 再使用主键的值通过聚集索引查找到需要的数据。
参考:其他索引方式
17.索引对性能有哪些影响?
优点:提高查询速度
将随机 I/O 变顺序I/O
减少数据库服务器需要扫描的数据量
帮助数据库服务器避免排序和临时表
唯一索引,能保证数据的唯一性
缺点:
索引的创建和维护耗时随着数据量的增加而增加
对表中数据进行增删改时,索引也要动态维护,降低了数据的维护速度
增大磁盘占用
事务
18.数据库事务四大特性?
原子性(Atomic):一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样
一致性(Consistency):在事务开始之前和事务结束以后, 数据库的完整性没有被破坏
隔离性(Isolation):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)
持久性(Durability):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失
19.ACID、BASE和CAP?
参考:DDBS BASE
参考:DDBS CAP
20.数据隔离级别:解释脏读、不可重复读,幻读,更新丢失
- 脏读(Dirty read):在一个事务中读取到另一个事务已经修改但没有提交的数据。
例如,事务A对数据进行了修改,但是还没有提交,这时事务B读取这个数据,然后事务A回滚,那么事务B取的数据无效。
解决办法:把数据库的事务隔离级别调整到READ_COMMITTED
- 不可重复读(NonRepeatable Read):不能读到相同的数据内容,事务A读取到了事务B已经提交的修改数据(即一个事务范围内两个相同的查询却返回了不同数据)。
例如事务A先读取数据,然后事务B对该同一数据修改并提交,那么事务A再次读取该数据时,由于事务B对该数据的修改,事务A两次读到的的数据可能是不一样的。
解决办法:把数据库的事务隔离级别调整到REPEATABLE_READ
- 幻读(Phantom Read):事务在插入已经检查过不存在的记录时,惊奇的发现这些数据已经存在了(针对的insert操作) 。
在事务A查询结束后,事务B往User表中插入了一条id为1的数据。此时,由于事务A查询到id为1的用户不存在,因此插入1条id为1的数据,报错:主键冲突。
解决办法:把数据库的事务隔离级别调整到SERIALIZABLE_READ
- 更新丢失(Update lose):两个事务同时操作相同数据,后提交的事务会覆盖先提交的事务处理结果。
解决办法:乐观锁
21.Mysql提供了哪几种事务隔离级别?
MySQL数据的四种隔离级别:
① Serializable (串行化):可避免脏读、不可重复读、幻读的发生。
② Repeatable read (可重复读):可避免脏读、不可重复读的发生。
③ Read committed (读已提交):可避免脏读的发生。
④ Read uncommitted (读未提交):最低级别,任何情况都无法保证。
通过SELECT @@transaction_isolation;
(mysql 5.7以下是select @@tx_isolation
)可以查看事务隔离级别:
隔离界别为可重复读,那么我写一个脏读和可重复读的事务:
# 事务A
begin;
select * from orders where id="190827F4AK12R30H";
commit;
# 事务B
begin;
UPDATE orders SET left_msg="new message" where id="190827F4AK12R30H";
select sleep(10);
commit;
查询结果是:尽管事务B修改了left_msg为new message,但是事务A的查询结果是old message。
# 事务A
begin;
select * from orders where id="190827F4AK12R30H";
select sleep(10);
select * from orders where id="190827F4AK12R30H";
commit;
# 事务B
begin;
UPDATE orders SET left_msg="new message" where id="190827F4AK12R30H";
commit;
查询结果是:尽管事务B修改了left_msg为new message,但是两次事务A的查询结果都是 old message。
最后试试幻读:
# 事务A
begin;
select * from stu where id=1;
select sleep(10);
insert into stu(id,name,age) values (1,"xiaoming",13);
commit;
# 事务B
begin;
insert into stu(id,name,age) values (1,"xiaohong",15);
commit;
运行结果:
可把事务级别改成串行set session transaction isolation level SERIALIZABLE;
。
使用set session transaction isolation level REPEATABLE READ;
改回来
22.简要说明InnoDB事务是如何通过日志来实现的?
小结:
事务的原子性是通过 undo log 来实现的
事务的持久性性是通过 redo log 来实现的
事务的隔离性是通过 (读写锁+MVCC)来实现的
redo log和undo log:
redo log记录数据被修改后的信息。undo log主要记录的是数据的逻辑变化,为了在发生错误时回滚之前的操作,需要将之前的操作都记录下来,然后在发生错误时才可以回滚
参考:高性能Mysql——InnoDB事务是如何通过日志来实现的?
23.二进制日志(binlog)的作用?与redo log的区别?
区别:
- redo log是在InnoDB存储引擎层产生,而binlog是MySQL数据库的上层产生的,并且二进制日志不仅仅针对INNODB存储引擎,MySQL数据库中的任何存储引擎对于数据库的更改都会产生二进制日志。
- 两种日志记录的内容形式不同。MySQL的binlog是逻辑日志,其记录是对应的DDL和DML语句。而innodb存储引擎层面的重做日志是物理日志,记录的是每个页的修改。
- 两种日志与记录写入磁盘的时间点不同,二进制日志只在事务提交完成后进行一次写入。而innodb存储引擎的重做日志在事务进行中不断地被写入,并日志不是随事务提交的顺序进行写入的。
- binlog可以作为恢复数据使用,主从复制搭建,redo log作为异常宕机或者介质故障后的数据恢复使用。
- binlog不是循环使用,在写满或者重启之后,会生成新的binlog文件,redo log是循环使用。
24.说一说MySQL中的锁机制
按粒度分:
- 表级锁:粒度最大的一种锁,表示对当前操作的整张表加锁。开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低
- 行级锁:粒度最小的一种锁,表示只针对当前操作的行进行加锁。开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度最高
- 页级锁:粒度介于行级锁和表级锁中间的一种锁。开销、加锁时间和并发度界于表锁和行锁之间;会出现死锁
按操作分:
- 读锁(共享锁):针对同一份数据,多个读取操作可以同时进行,不互相影响
- 写锁(排它锁):当前写操作没有完成前,会阻断其他写锁和读锁
行级死锁
25.InnoDB的行锁/表锁?*
mysql的行锁是通过索引加载的,即是行锁是加在索引响应的行上的。
要是对应的SQL语句没有走索引,则会全表扫描,此时取而代之的是表锁。
表锁:不会出现死锁,发生锁冲突几率高,并发低。(表锁总是一次性获得所需的全部锁,要么全部满足,要么全部等待。所以不会产生死锁。)
行锁:会出现死锁,发生锁冲突几率低,并发高。
行锁分 共享锁 和 排它锁。
共享锁又称:读锁。当一个事务对某几行上读锁时,允许其他事务对这几行进行读操作,但不允许其进行写操作,也不允许其他事务给这几行上排它锁,但允许上读锁。
select math from zje where math>60 lock in share mode;
排它锁又称:写锁。当一个事务对某几个上写锁时,不允许其他事务写,但允许读。更不允许其他事务给这几行上任何锁。包括写锁。
select math from zje where math >60 for update;
26.什么是MVCC ?
MVCC全称是: Multiversion concurrency control,多版本控制: 指的是一种提高并发的技术。
一般我们认为MVCC有下面几个特点(也就是乐观锁的一种实现):
- 每行数据都存在一个版本,每次数据更新时都更新该版本。(其实有删除的版本号和更新的版本号两种)
- 修改时Copy出当前版本(做快照), 然后随意修改,各个事务之间无干扰
- 保存时比较版本号,如果成功(commit),则覆盖原记录, 失败则放弃copy(rollback)
- 就是每行都有版本号,保存时根据版本号决定是否成功,听起来含有乐观锁的味道, 因为这看起来正是,在提交的时候才能知道到底能否提交成功
而InnoDB实现MVCC的方式是:
- 事务以排他锁的形式修改原始数据
- 把修改前的数据存放于undo log,通过回滚指针与主数据关联
- 修改成功(commit)啥都不做,失败则恢复undo log中的数据(rollback)
从某种意义上来说,InnoDB的MVCC并非真正的MVCC,他只是借用MVCC的名号实现了读的非阻塞而已。
MVCC只在 READ COMMITTED 和 REPEATABLE READ 两个隔离级别下工作。其他两个隔离级别够和MVCC不兼容, 因为 READ UNCOMMITTED 总是读取最新的数据行, 而不是符合当前事务版本的数据行。而 SERIALIZABLE 则会对所有读取的行都加锁。
参考:MVCC
27.什么是意向锁?*
在支持多层锁定的数据库中,若事务在某子节点持有读/写锁,则事务在其父节点持有意向锁。
意向锁的含义是如果对一个结点加意向锁,则说明该结点的下层结点正在被加锁;对任一结点加锁时,必须先对它的上层结点加意向锁。
例如,对任一元组加锁时,必须先对它所在的关系加意向锁。于是,事务T要对关系R1加 X锁时,系统只要检查根结点数据库和关系R1是否己加了不相容的锁,而不再需要搜索和检查尺中的每一个元组是否加了X锁。
下面介绍三种常用的意向锁:意向共享锁(Intent Share Lock,简称IS锁);意向排它锁(Intent Exclusive Lock,简称IX锁);共享意向排它锁(Share Intent Exclusive Lock,简称SIX锁)。
意向锁在原来的 X/S 锁之上引入了 IX/IS,IX/IS 都是表锁,用来表示一个事务想要在表中的某个数据行上加 X 锁或 S 锁。 有以下两个规定:
- 一个事务在获得某个数据行对象的 S 锁之前,必须先获得表的 IS 锁或者更强的锁;
- 一个事务在获得某个数据行对象的 X 锁之前,必须先获得表的 IX 锁。
通过引入意向锁,事务 T 想要对表 A 加 X 锁,只需要先检测是否有其它事务对表 A 加了 X/IX/S/IS 锁,如果加了就表示有其它事务正在使用这个表或者表中某一行的锁,因此事务 T 加 X 锁失败。
引进意向锁是为了提高封锁子系统的效率。该封锁子系统支持多种封锁粒度。原因是:在多粒度封锁方法中一个数据对象可能以两种方式加锁 ― 显式封锁和隐式封锁。因此系统在对某一数据对象加锁时不仅要检查该数据对象上有无(显式和隐式)封锁与之冲突,还要检查其所有上级结点和所有下级结点,看申请的封锁是否与这些结点上的(显式和隐式)封锁冲突,显然,这样的检查方法效率很低。
考虑这个例子:
事务A锁住了表中的一行,让这一行只能读,不能写。之后,事务B申请整个表的写锁。如果事务B申请成功,那么理论上它就能修改表中的任意一行,这与A持有的行锁是冲突的。数据库需要避免这种冲突,就是说要让B的申请被阻塞,直到A释放了行锁。
如果不使用意向锁,那么需要两个判断:
- 判断表是否已被其他事务用表锁锁表
- 判断表中的每一行是否已被行锁锁住(效率太低)
在意向锁存在的情况下,事务A必须先申请表的意向共享锁,成功后再申请一行的行锁。
这样只需要下面两个判断:
- 判断表是否已被其他事务用表锁锁表
- 判断表上是否有意向共享锁
若有,说明表中有些行被共享行锁锁住了,因此,事务B申请表的写锁会被阻塞。