数据库部分
本文参考马士兵java面试八股文,进行了梳理和分类,作为面试笔记留档
事务的基本特性
事务四大特征ACID:原子性,一致性,隔离性和持久性。
原子性(Atomicity)
一个原子事务要么完整执行,要么干脆不执行。这意味着,工作单元中的每项任务都必须正确执行。如果有任一任务执行失败,则整个工作单元或事务就会被终止。即此前对数据所作的任何修改都将被撤销。如果所有任务都被成功执行,事务就会被提交,即对数据所作的修改将会是永久性的。
一致性(Consistency)
它必须由事务系统和应用开发人员共同来保证。事务系统通过保证事务的原子性,隔离性和持久性来满足这一要求; 应用开发人员则需要保证数据库有适当的约束(主键,引用完整性等),并且工作单元中所实现的业务逻辑不会导致数据的不一致(即,数据预期所表达的现实业务情况不相一致)。例如,在一次转账过程中,从某一账户中扣除的金额必须与另一账户中存入的金额相等。你的余额为100,你读到余额要取,有人向你转100,但是事务没提交(这时候你读到的余额应该是100,而不是200) 这种就是一致性。
隔离性(Isolation)
隔离性意味着事务必须在不干扰其他进程或事务的前提下独立执行。换言之,在事务或工作单元执行完毕之前,其所访问的数据不能受系统其他部分的影响。
持久性(Durability)
持久性表示在某个事务的执行过程中,对数据所作的所有改动都必须在事务成功结束前保存至某种物理存储设备。这样可以保证,所作的修改在任何系统瘫痪时不至于丢失。
ACID是靠什么保证的
原子性:由undolog日志来保证,它记录了需要回滚的日志信息,事务回滚时撤销已经执行成功的sql
一致性:由原子性,隔离性,持久性三大特性保证,程序代码要保证业务上的一致性
隔离性:由MVCC(多版本并发控制)来保证
持久性:由redolog来保证,mysql修改数据时,会在redolog中记录一份日志信息,就算数据没有保存成功,只要日志保存成功了,数据仍然不会丢失。
MySQL的隔离级别
读未提交
在这个隔离级别,所有事务都可以“看到”未提交事务的执行结果。可能会引起”脏读”问题。本隔离级别很少用于实际应用,因为它的性能也不必其他性能好多少,而别的级别还有其他更多的优点。
脏读:就是读取了尚未提交的数据,使得前后两次读取的数据不一致,就好像是读到了”脏数据”。比如一个事务a要读取账户余额,读到你的账户余额为100,而就在此时,另一个事务b在用你的账户进行转账操作,要转出50,只不过还未提交,这时实际你的账户余额应该为50,但事务a读到的是100,读到的100就是未提交的”脏数据”
读已提交
大多数数据库系统的默认隔离级别(但是不是MySQL的默认隔离级别),就是只能读取已提交的数据。满足了隔离的早先简单定义:一个事务开始时,只能“看见”已经提交事务所做的改变,一个事务从开始到提交前,所做的任何数据改变都是不可见的,除非已经提交。这种隔离级别会出现“不可重复读”问题,也就是用户运行同一个语句两次,看到的结果是不同的。
不可重复读:就是在一个事务执行过程中,对同一数据的前后两次读取结果可能不同。还是上面的例子,在读已提交隔离级别下,如果别的事务正在对你的账户余额进行update操作,那么在他提交前,你无法读取该数据,但是如果事务a开始时,账户余额没有被其他事务进行更新操作,那么你就可以读取账户余额为100,事务a在未提交前(事务a只有查询操作),事务b可以对账户余额进行更新操作,并且如果在a提交前提交,那么a如果在b提交后再次读取账户余额,会发现与之前不同,就出现了”不可重复读问题”。
不可重复读同脏读的区别在于,脏读是一个事务读取了另一未完成的事务执行过程中的数据,而不可重复读是一个事务执行过程中,另一事务提交并修改了当前事务正在读取的数据。
可重复读
MySQL数据库默认的隔离级别。它保证同一事务的多个实例在并发读取事务时,会“看到同样的”数据行。解决了”不可重复读”问题,但会出现”幻读”问题。
幻读:幻读是事务非独立执行时发生的一种现象,例如事务a批量对一个表中某一列列值为1的数据修改为2的变更,但是在这时,事务b对这张表插入了一条列值为1的数据,并完成提交。此时,如果事务a查看刚刚完成操作的数据,发现还有一条列值为1的数据没有进行修改,而这条数据其实是b刚刚提交插入的,这就是幻读。
幻读和不可重复读都是读取了另一条已经提交的事务(这点同脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。
串行化
该级别是最高级别的隔离级。它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简而言之,SERIALIZABLE是在每个读的数据行上加锁。在这个级别,可能导致大量的超时Timeout和锁竞争Lock Contention现象,实际应用中很少使用到这个级别,但如果用户的应用为了数据的稳定性,需要强制减少并发的话,也可以选择这种隔离级。
mysql的主从复制
MySQL 主从复制是指数据可以从一个MySQL数据库服务器主节点复制到一个或多个从节点。MySQL 默认采用异步复制方式,这样从节点不用一直访问主服务器来更新自己的数据,数据的更新可以在远程连接上进行,从节点可以复制主数据库中的所有数据库或者特定的数据库,或者特定的表。
mysql为什么需要主从同步
- 在业务复杂的系统中,有这么一个情景,有一句sql语句需要锁表,导致暂时不能使用读的服务,那么就很影响运行中的业务,使用主从复制,让主库负责写,从库负责读,这样,即使主库出现了锁表的情景,通过读从库也可以保证业务的正常运作。
- 做数据的热备
- 架构的扩展。业务量越来越大,I/O访问频率过高,单机无法满足,此时做多库的存储,降低磁盘I/O访问的频率,提高单个机器的I/O性能。
mysql复制原理
- master服务器将数据的改变记录二进制binlog日志,当master上的数据发生改变时,则将其改变写入二进制日志中;
- slave服务器会在一定时间间隔内对master二进制日志进行探测其是否发生改变,如果发生改变,则开始一个I/OThread请求master二进制事件
- 同时主节点为每个I/O线程启动一个dump线程,用于向其发送二进制事件,并保存至从节点本地的中继日志中,从节点将启动SQL线程从中继日志中读取二进制日志,在本地重放,使得其数据和主节点的保持一致,最后I/OThread和SQLThread将进入睡眠状态,等待下一次被唤醒。
也就是说:
- 从库会生成两个线程,一个I/O线程,一个SQL线程;
- I/O线程会去请求主库的binlog,并将得到的binlog写到本地的relay-log(中继日志)文件中;
- 主库会生成一个log dump线程,用来给从库I/O线程传binlog;
- SQL线程,会读取relay log文件中的日志,并解析成sql语句逐一执行;
注意:
- master将操作语句记录到binlog日志中,然后授予slave远程连接的权限(master一定要开启binlog二进制日志功能;通常为了数据安全考虑,slave也开启binlog功能)
- slave开启两个线程:IO线程和SQL线程。其中:IO线程负责读取master的binlog内容到中继日志relay log里;SQL线程负责从relay log日志里读出binlog内容,并更新到slave的数据库里,这样就能保证slave数据和master数据保持一致了。
- Mysql复制至少需要两个Mysql的服务,当然Mysql服务可以分布在不同的服务器上,也可以在一台服务器上启动多个服务。
- Mysql复制最好确保master和slave服务器上的Mysql版本相同(如果不能满足版本一致,那么要保证master主节点的版本低于slave从节点的版本)
- master和slave两节点间时间需同步
mysql聚簇和非聚簇索引的区别
mysql的索引类型跟存储引擎是相关的,innodb存储引擎数据文件跟索引文件全部放在ibd文件中,而myisam的数据文件放在myd文件中,索引放在myi文件中,其实区分聚簇索引和非聚簇索引非常简单,只要判断数据跟索引是否存储在一起就可以了。
innodb存储引擎在进行数据插入的时候,数据必须要跟索引放在一起,如果有主键就使用主键,没有主键就使用唯一键,没有唯一键就使用6字节的rowid,因此跟数据绑定在一起的就是聚簇索引,而为了避免数据冗余存储,其他的索引的叶子节点中存储的都是聚簇索引的key值,因此innodb中既有聚簇索引也有非聚簇索引,而myisam中只有非聚簇索引。
索引的基本原理
为什么要有索引?
一般的应用系统,读写比例在10:1左右,而且插入操作和一般的更新操作很少出现性能问题,在生产环境中,我们遇到最多的,也是最容易出问题的,还是一些复杂的查询操作,因此对查询语句的优化显然是重中之重。说起加速查询,就不得不提到索引了。
什么是索引?
索引在MySQL中也叫是一种“键”,是存储引擎用于快速找到记录的一种数据结构。索引对于良好的性能非常关键,尤其是当表中的数据量越来越大时,索引对于性能的影响愈发重要。
索引优化应该是对查询性能优化最有效的手段了。索引能够轻易将查询性能提高好几个数量级。
索引相当于字典的音序表,如果要查某个字,如果不使用音序表,则需要从几百页中逐页去查。
索引的原理
索引的目的在于提高查询效率,与我们查阅图书所用的目录是一个道理:先定位到章,然后定位到该章下的一个小节,然后找到页数。相似的例子还有:查字典,查火车车次,飞机航班等
本质都是:通过不断地缩小想要获取数据的范围来筛选出最终想要的结果,同时把随机的事件变成顺序的事件,也就是说,有了这种索引机制,我们可以总是用同一种查找方式来锁定数据。
数据库也是一样,但显然要复杂的多,因为不仅面临着等值查询,还有范围查询(>、<、between、in)、模糊查询(like)、并集查询(or)等等。数据库应该选择怎么样的方式来应对所有的问题呢?我们回想字典的例子,能不能把数据分成段,然后分段查询呢?最简单的如果1000条数据,1到100分成第一段,101到200分成第二段,201到300分成第三段…这样查第250条数据,只要找第三段就可以了,一下子去除了90%的无效数据。但如果是1千万的记录呢,分成几段比较好?按照搜索树的模型,其平均复杂度是lgN,具有不错的查询性能。但这里我们忽略了一个关键的问题,复杂度模型是基于每次相同的操作成本来考虑的。而数据库实现比较复杂,一方面数据是保存在磁盘上的,另外一方面为了提高性能,每次又可以把部分数据读入内存来计算,因为我们知道访问磁盘的成本大概是访问内存的十万倍左右,所以简单的搜索树难以满足复杂的应用场景。
索引的数据结构
MySQL主要用到两种结构:B+ Tree索引和Hash索引
Inodb存储引擎 默认是 B+Tree索引
Memory 存储引擎 默认 Hash索引;
MySQL中,只有Memory(Memory表只存在内存中,断电会消失,适用于临时表)存储引擎显示支持Hash索引,是Memory表的默认索引类型,尽管Memory表也可以使用B+Tree索引。Hash索引把数据以hash形式组织起来,因此当查找某一条记录的时候,速度非常快。但是因为hash结构,每个键只对应一个值,而且是散列的方式分布。所以它并不支持范围查找和排序等功能。
B+Tree是mysql使用最频繁的一个索引数据结构,是InnoDB和MyISAM存储引擎模式的索引类型。相对Hash索引,B+Tree在查找单条记录的速度比不上Hash索引,但是因为更适合排序等操作,所以它更受欢迎。毕竟不可能只对数据库进行单条记录的操作。
对比:
hash类型的索引:查询单条快,范围查询慢
btree类型的索引:b+树,层数越多,数据量指数级增长(我们就用它,因为innodb默认支持它)
mysql索引结构及各自的优劣
索引的数据结构和具体存储引擎的实现有关,mysql中使用较多的索引有hash索引,B+树索引,innodb的索引实现为B+树,memory存储引擎为hash索引。
B+树是一个平衡的多叉树,从根节点到每个叶子节点的高度差值不超过1,而且同层级的二节点间有指针相关连接,在B+树上的常规检索,从根节点到叶子节点的搜索效率基本相当,不会出现大幅波动,而且基于索引的顺序扫描时,也可以利用双向指针快速左右移动,效率非常高。因为,B+树索引被广泛应用于数据库、文件系统等场景。
哈希索引就是采用一定的哈希算法,把键值换算成新的哈希值,检索时不需要类似B+树那样从根节点到叶子节点逐级查找,只需一次哈希算法即可立刻定位到相应的位置,速度非常快。
如果是等值查询,那么哈希索引明显有绝对优势,因为只需要经过一次算法即可找到相应的键值,前提是键值都是唯一的。如果键值不是唯一的,就需要先找到该键所在位置,然后再根据链表往后扫描,知道找到对应的数据
如果是范围查询检索,这时候哈徐索引就毫无用武之地了,因为原先是有序的键值,经过哈希算法后,有可能变成不连续的了,就没办法再利用索引完成范围查询检索
哈希所有也没办法利用索引完成排序,以及like这样的部分模糊查询
哈希索引也不支持多列联合索引的最左匹配规则
B+树索引的关键字检索效率比较平均,不像B树那样波动大,在有大量重复键值情况下,哈希索引的效率也是极低的,因此存在哈希碰撞问题。
mysql中索引类型以及对数据库的性能的影响
- 普通索引:允许被索引的数据列包含重复的值
- 唯一索引:可以保证数据记录的唯一性
- 主键索引:是一种特殊的唯一索引,在一张表中只能定义一个主键索引,主键用于唯一标识一条记录,使用关键字primary key来创建
- 联合索引:索引可以覆盖多个数据列
- 全文索引:通过建立倒排索引,可以极大的提升检索效率,解决判断字段是否包含的问题,是目前搜索引擎使用的一种关键技术
索引可以极大地提高数据的查询速度。通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。但是会降低插入、删除、更新表的速度,因为在执行这些写操作的时候,还要操作索引文件
索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要简历聚簇索引,那么需要的空间就会更大,如果非聚簇索引很多,一旦聚簇索引改变,那么所有非聚簇索引都会跟着变
平衡二叉树,B树,B+树,B*树
从二分法到二叉树
二分法是我们常用的一种查找算法,可以有效的提升数据找找的效率,其实现思路是:
- 首先对数据集进行排序。
- 找到数据集中间位置的节点。
- 用查找的条件和中间节点进行比较,等于则直接返回,中间节点数据小于查找条件则说明数据在排序列表的左边,大于则说明数据在排序列表的右边。
从二分法查找的过程来看,如果能保证数据的有序性,并且预先把数据进行分段存储好数据的中间节点,那么查找的时候就会很简单,所以如果要使用二分法,我们通常都会在数据存储的时候就预先把数据整理成适合二分法查找的数据结构,这就演化出了树和跳表两种数据结构,比如我们把上面的[9,2,6,5,7,8,4,3,1] 构建成一个二叉搜素树,那么就是下面这样
二叉搜索树:又叫二叉查找树/。特点是左孩子比父节点小,右孩子比父节点大,还有一个特性就是”中序遍历“可以让结点有序。
从二叉查找树到平衡二叉树
二叉查找树充分了利用二分法的思维提升了数据查找的效率,不过我们在构建二叉树的时候就会发现很容易出现一个问题, 就是二叉查找树的“高度”不稳定,同样的数据插入的先后顺序不一样,就很有可能变成下图一样的结构。
如果树的节点变成线性结构,那么就会极大的降低我们的查询效率,所以我们必须要有一种方式来保证二叉树节点的平衡,让树的节点高度差不会太大,这个时候就衍生了一些平衡算法,最终我们的二叉树就有像AVL树和红黑树这些新产品,我们也称这些新产品为平衡二叉树,平衡二叉树通常会保证树的左右两边的节点层级相差不会大于2。
层级: 根结点的层级为1,其余结点的层级等于该结点的双亲结点的层级加1
平衡二叉树
上面提到,构建二叉搜索树时,节点的插入顺序可能会导致构建树的高度不同,或者说二叉树不够平衡,导致查找效率低下。所以为了保证查找效率,出现了平衡二叉树的结构。平衡二叉树采用平衡算法可以让数据均匀的分布到树里的各个节点,避免树的高度相差太多,从而解决二叉树的层级不稳定问题。
平衡二叉树的构建规则:
- 非叶子节点只能允许最多两个子节点存在。
- 每一个节点的左边子节点值小于当前节点,右边的子节点值大于当前节点(这里的“值”是基于自己的算法规则而定的,比如hash值);
- 通过平衡算法(比如Treap、AVL、红黑树),保证树左右节点的高度相差不超过2层。
B树(B-tree)
B树和平衡二叉树的不同之处是:B树属于多叉树又名平衡多路查找树(查找路径不止两个),数据库索引技术里大量使用着B树和B+树的数据结构。
B树的构建规则:
- 排序方式:所有节点关键字是按递增次序排列,并遵循左小右大原则;
- 子节点数:非叶节点(根节点和枝节点)的子节点数 >1、且子节点数量<=M 、且M>=2,空树除外(注:M阶代表一个树节点最多有多少个查找路径,M=M路,当M=2则是2叉树,M=3则是3叉)
- 关键字数:枝节点的关键字数量大于等于ceil(m/2)-1个且小于等于M-1个(注:ceil()是个朝正无穷方向取整的函数 如ceil(1.1)结果为2);
- 所有叶子节点均在同一层、叶子节点除了包含了关键字 和 关键字记录的指针外,也有指向其子节点的指针只不过其指针地址都为null对应下图最后一层节点的空格子;
B树的查询流程:
如上图我要从上图中找到E字母,查找流程如下
(1)获取根节点的关键字进行比较,当前根节点关键字为M,E<M(26个字母顺序),所以往找到指向左边的子节点
(2)拿到关键字D和G,D<E<G 所以直接找到D和G中间的节点;
(3)拿到E和F,因为E=E 所以直接返回关键字和指针信息(如果树结构里面没有包含所要查找的节点则返回null);
B树的插入节点流程
定义一个5阶树(平衡5路查找树;),现在我们要把3、8、31、11、23、29、50、28、53 这些数字构建出一个5阶树出来。
- 节点拆分规则:当前是要组成一个5路查找树,那么此时m=5,关键字数必须<=5-1(这里关键字数>4就要进行节点拆分)。
- 排序规则:满足节点本身比左边节点大,比右边节点小的排序规则。
首先插入 3、8、31、11
然后插入23、29
最后插入50、28、53
B树节点的删除
节点合并规则:
- 当前是要组成一个5路查找树,那么此时m=5,关键字数必须大于等于 ceil(m/2)-1(所以这里关键字数<2就要进行节点合并)
- 满足节点本身比左边节点大,比右边节点小的排序规则。
- 关键字数小于二时先从子节点取,子节点没有符合条件时就向父节点取,取中间值往父节点放。
B树特点:
B树相对平衡二叉树在节点空间的利用率上进行改进,B树在每个节点保存更多的数据,减少了树的高度,从而提升了查找的性能,在数据库应用中,B树的每个节点存储的数据量大约为4K, 这是因为考虑到磁盘数据存储是采用块的形式存储的,每个块的大小为4K,每次对磁盘进行IO数据读取时,同一个磁盘块的数据会被一次性读取出来,所以每一次磁盘IO都可以读取到B树中一个节点的全部数据。
B+树
B+树是在B树的基础上又一次的改进,其主要对两个方面进行了提升,一方面是查询的稳定性,另外一方面是在数据排序方面更友好。
B+树构建规则
- B+树的非叶子节点不保存具体的数据,而只保存关键字的索引,而所有的数据最终都会保存到叶子节点。因为所有数据必须要到叶子节点才能获取到,所以每次数据查询的次数都一样,这样一来B+树的查询速度也就会比较稳定,而B树的查找过程中,不同的关键字查找的次数很有可能都是不同的(有的数据可能在根节点,有的数据可能在最下层的叶节点),所以在数据库的应用层面,B+树就显得更合适。
- B+树叶子节点的关键字从小到大有序排列,左边结尾数据都会保存右边节点开始数据的指针。因为叶子节点都是有序排列的,所以B+树对于数据的排序有着更好的支持。
- 非叶子节点的子节点数=关键字数(来源百度百科)(根据各种资料 这里有两种算法的实现方式,另一种为非叶节点的关键字数=子节点数-1(来源维基百科),虽然他们数据排列结构不一样,但其原理还是一样的Mysql 的B+树是用第一种方式实现)
(百度百科算法结构示意图)
B+树和B树的对比
- B+树查询速度更稳定:B+所有关键字数据地址都存在叶子节点上,所以每次查找的次数都相同所以查询速度要比B树更稳定。
- B+树天然具备排序功能:B+树所有的叶子节点数据构成了一个有序链表,在查询大小区间的数据时候更方便,数据紧密性很高,缓存的命中率也会比B树高。
- B+树全节点遍历更快:B+树遍历整棵树只需要遍历所有的叶子节点即可,而不需要像B树一样需要对每一层进行遍历,这有利于数据库做全表扫描。
B树相对于B+树的优点是:如果经常访问的数据离根节点很近,因为B树的非叶子节点本身存有关键字和数据,所以在查询这种数据检索的时候会要比B+树快。
B*树
很显然,B*树是对B+数的再一次改进,在B+树的构建过程中,为了保持树的平衡,节点的合并拆分是比较耗费时间的,所以B*树就是在如何减少构建中节点合并和拆分的次数,从而提升树的数据插入、删除性能。
B*树构建规则
相对于B+树B*的不同之处如下:
- 首先是关键字个数限制问题,B+树初始化的关键字初始化个数是cei(m/2),B*树的初始化个数为(cei(2/3*m))
- B+树节点满时就会分裂,而B*树节点满时会检查兄弟节点是否满(因为每个节点都有指向兄弟的指针),如果兄弟节点未满则向兄弟节点转移关键字,如果兄弟节点已满,则从当前节点和兄弟节点各拿出1/3的数据创建一个新的节点出来;
B*树 与B+树对比
在B+树的基础上因其初始化的容量变大,使得节点空间使用率更高,而又存有兄弟节点的指针,可以向兄弟节点转移关键字的特性使得B*树额分解次数变得更少;
总结
相同思想和策略:
从平衡二叉树、B树、B+树、B*树总体来看它们的贯彻的思想是相同的,都是采用二分法和数据平衡策略来提升查找数据的速度;
不同的方式对树的不断优化:
- 首先,为了保证树的节点均匀分布,所以在二叉树的基础上加上了平衡算法,就有了平衡二叉树。
- 为了减少树的高度,所以B树一个节点下面可以添加N个子节点,然后每个节点的大小限制在磁盘块容量大小,让节点只需要通过一次I/O就能读取到所有数据,通过增加节点存储的数据减少了树的高度,而节点的数据变多并没有让IO次数变多。
- B+树在B树的基础上,在查询的稳定性 和排序方面进行了优化,因为B+树所有的数据都会保存到叶子节点,然后所有叶子节点本身是有序的。
- B*树为了减少 树在构建过程中节点的拆分、合并次数,所以在每个节点上都保存了旁边节点的指针,在节点需要进行拆分、合并时,优先从旁边节点挪数据,从而减少构建过程中节点拆分、合并的次数,提升了树的构建性能
索引的设计原则
在进行索引设计的时候,应该保证索引字段占用的空间越小越好,这只是一个大的方向,还有一些细节点需要注意下:
- 适合索引的列是出现在where字句中的列,或者连接子句中指定的列
- 基数较小的表,索引效果差,没必要创建索引
- 在选择索引列的时候,越短越好,可以指定某些列的一部分,没必要用全部字段的值
- 不要给表中的每一个字段都创建索引,并不是索引越多越好
- 定义有外键的数据列一定要创建索引
- 更新频繁的字段不要有索引
- 创建索引的列不要过多,可以创建组合索引,但是组合索引的列的个数不建议太多
- 大文本、大对象不要创建索引
mysql锁的类型
- 基于锁的属性分类:共享锁、排他锁。
- 基于锁的粒度分类:行级锁(innodb )、表级锁( innodb 、myisam)、页级锁( innodb引擎)、记录锁、间隙锁、临键锁。
- 基于锁的状态分类:意向共享锁、意向排它锁。
共享锁(share lock): 共享锁又称读锁,简称 S 锁;当一个事务为数据加上读锁之后,其他事务只能对该数据加读锁,而不能对数据加写锁,直到所有的读锁释放之后其他事务才能对其进行加持写锁。共享锁的特性主要是为了支持并发的读取数据,读取数据的时候不支持修改,避免出现重复读的问题。
排他锁(exclusive lock):排他锁又称写锁,简称 X 锁;当一个事务为数据加上写锁时,其他请求将不能再为数据加任何锁,直到该锁释放之后,其他事务才能对数据进行加锁。排他锁的目的是在数据修改时候,不允许其他人同时修改,也不允许其他人读取,避免了出现脏数据和脏读的问题。
表锁(table lock):表锁是指上锁的时候锁住的是整个表,当下一个事务访问该表的时候,必须等前一个事务释放了锁才能进行对表进行访问;特点:粒度大,加锁简单,容易冲突;
行锁:行锁是指上锁的时候锁住的是表的某一行或多行记录,其他事务访问同一张表时,只有被锁住的记录不能访问,其他的记录可正常访问,特点:粒度小,加锁比表锁麻烦,不容易冲突,相比表锁支持的并发要高
记录锁(Record lock):记录锁也属于行锁中的一种,只不过记录锁的范围只是表中的某一条记录,记录锁是说事务在加锁后锁住的只是表的某一条记录,加了记录锁之后数据可以避免数据在查询的时候被修改的重复读问题,也避免了在修改的事务未提交前被其他事务读取的脏读问题
页锁:页级锁是 MysQL 中锁定粒度介于行级锁和表级锁中间的一种锁.表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。特点:开销和加锁时间界于表锁和行锁之间,会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。
间隙锁:是属于行锁的一种,间隙锁是在事务加锁后其锁住的是表记录的某一个区间,当表的相邻ID之间出现空隙则会形成一个区间,遵循左开右闭原则。范围查询并且查询未命中记录,查询条件必须命中索引、间隙锁只会出现在REPEATABLE_READ(重复读)的事务级别中。
临键锁(Next-Key lock):也属于行锁的一种,并且它是INNODB的行锁默认算法,总结来说它就是记录锁和间隙锁的组合,临键锁会把查询出来的记录锁住,同时也会把该范围查询内的所有间隙空间也会锁住,再之它会把相邻的下一个区间也会锁住。
怎么处理MySQL的慢查询
- 开启慢查询日志,准确定位到哪个sql语句出现了问题
- 分析sql语句,看看是否load了额外的数据,可能是查询了多余的行并且抛弃掉了,可能是加载了许多结果中并不需要的列,对语句进行分析以及重写
- 分析语句的执行计划,然后获得其使用索引的情况,之后修改语句或者修改索引,使得语句可以尽可能的命中索引
- 分析语句的执行计划,然后获得其使用索引的情况,之后修改语句或者修改索引,使得语句可以尽可能的命中索引
Mybait的优缺点
优点:
- 简单易学,容易上手(相比于Hibernate) 基于SQL编程;
- JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接;
- 很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持,而JDBC提供了可扩展性,所以只要这个数据库有针对Java的jar包就可以就可以与MyBatis兼容),开发人员不需要考虑数据库的差异性。
- 提供了很多第三方插件(分页插件 / 逆向工程);
- 能够与Spring很好的集成;
- MyBatis相当灵活,不会对应用程序或者数据库的现有设计强加任何影响,SQL写在XML里,从程序代码中彻底分离,解除sql与程序代码的耦合,便于统一管理和优化,并可重用。
- 提供XML标签,支持编写动态SQL语句。
- 提供映射标签,支持对象与数据库的ORM字段关系映射。
- 提供对象关系映射标签,支持对象关系组建维护。
缺点:
- SQL语句的编写工作量较大,尤其是字段多、关联表多时,更是如此,对开发人员编写SQL语句的功底有一定要求。
- SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。
mybatis中#{}和${}的区别
#{}是预编译处理,${}是字符串替换。
- mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值。
- mybatis在处理${}时,就是把${}替换成变量的值。
- 使用#{}可以有效的防止SQL注入,提高系统安全性。原因在于:预编译机制。预编译完成之后,SQL的结构已经固定,即便用户输入非法参数,也不会对SQL的结构产生影响,从而避免了潜在的安全风险。
- 预编译是提前对SQL语句进行预编译,而其后注入的参数将不会再进行SQL编译。我们知道,SQL注入是发生在编译的过程中,因为恶意注入了某些特殊字符,最后被编译成了恶意的执行操作。而预编译机制则可以很好的防止SQL注入。
mybatis插件运行原理及开发流程
mybatis只支持针对ParameterHandler、ResultSetHandler、StatementHandler、Executor这四种接口的插件,mybatis使用jdk的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这四种接口对象的方法时,就会进入拦截方法,具体就是InvocationHandler
的invoke方法,拦截那些你指定需要拦截的方法。
编写插件:实现Mybatis的Interceptor接口并复写intercept方法,然后给插件编写注解,指定要拦截哪一个接口的哪些方法,在配置文件中配置编写的插件即可。
1 |
MVCC
MVCC
MVCC,全称Multi-Version Concurrency Control,即多版本并发控制。MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。
MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理读写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读。
当前读
像select lock in share mode(共享锁), select for update ; update, insert ,delete(排他锁)这些操作都是一种当前读,为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。
快照读(提高数据库的并发查询能力)
像不加锁的select操作就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即MVCC,可以认为MVCC是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本
当前读、快照读、MVCC关系
MVCC多版本并发控制指的是维持一个数据的多个版本,使得读写操作没有冲突,快照读是MySQL为实现MVCC的一个非阻塞读功能。MVCC模块在MySQL中的具体实现是由三个隐式字段,undo日志、read view三个组件来实现的。
MVCC解决的问题
数据库并发场景有三种,分别为:
- 读读:不存在任何问题,也不需要并发控制
- 读写:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读、幻读、不可重复读
- 写写:有线程安全问题,可能存在更新丢失问题
MVCC是一种用来解决读写冲突的无锁并发控制,也就是为事务分配单项增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照,所以MVCC可以为数据库解决一下问题:
- 在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能
- 解决脏读、幻读、不可重复读等事务隔离问题,但是不能解决更新丢失问题
MVCC实现原理
简述Myisam和Innodb的区别+
InnoDB存储引擎
主要面向OLTP(Online Transaction Processing,在线事务处理)方面的应用,是第一个完整支持ACID事务的存储引擎(BDB第一个支持事务的存储引擎,已经停止开发)。
特点:
- 支持行锁
- 支持外键
- 支持自动增加列AUTO_INCREMENT属性
- 支持事务
- 支持MVCC模式的读写
- 读的效率低于MYISAM
- 写的效率高优于MYISAM
- 适合频繁修改以及设计到安全性较高的应用
- 清空整个表的时候,Innodb是一行一行的删除,
MyISAM存储引擎
是MySQL官方提供的存储引擎,主要面向OLAP(Online Analytical Processing,在线分析处理)方面的应用。
特点:
- 独立于操作系统,当建立一个MyISAM存储引擎的表时,就会在本地磁盘建立三个文件,例如我建立tb_demo表,那么会生成以下三个文件tb_demo.frm,tb_demo.MYD,tb_demo.MYI
- 不支持事务,
- 支持表锁和全文索引
- MyISAM存储引擎表由MYD和MYI组成,MYD用来存放数据文件,MYI用来存放索引文件。MySQL数据库只缓存其索引文件,数据文件的缓存交给操作系统本身来完成;
- MySQL5.0版本开始,MyISAM默认支持256T的单表数据;
- 选择密集型的表:MYISAM存储引擎在筛选大量数据时非常迅速,这是他最突出的优点
- 读的效率优于InnoDB
- 写的效率低于InnoDB
- 适合查询以及插入为主的应用
- 清空整个表的时候,MYISAM则会新建表