对于Web三大模块的后端数据库模块,只要你是做过项目都会有涉及与了解,无论你是简单的增删改查,对于所写SQL语句都需要去查询才能够完善,亦或是对SQL语句都熟烂于心,无论是单表的查询操作,或是多表的组合查询都能够信手拈来,但这些也都是SQL语句部分,对于SQL数据库的基础真的是太多太多,面试时候遇到的每一家公司问到的也都很少出现重复,但是主要的知识点也就摆在哪里。
在下面会详细总结出来自己面试时候遇到过的SQL数据库问题,和查阅别人的面经整理出来的SQL复习信息,主要分为基础,索引,锁,和SQL语句等相关,也是尽力来网罗目前的SQL面试的大部分题目来增添自己的知识储备。
第一范式:每个列都不可以再拆分
第二范式:在第一范式的基础上,非主键列完全依赖于主键,而不能是依赖于主键的一部分
第三范式:在第二范式的基础上,非主键列只依赖于主键,不依赖于其他非主键
回答这个问题时候,首先说明MySQL在什么时候使用到的是那个存储引擎,然后再回答两者之间的区别,也不需要将那么多的区别都回答出来,只需要回答出几个更改比较大的点
答: 对于MySQL来说在5.5 版本之前使用到的是MyISAM,在5.5版本之后默认的存储引擎就是InnoDB。
区别:
MyISAM | InnnoDB | |
---|---|---|
是否支持外键 | 不支持 | 支持 |
是否支持事务 | 不支持 | 支持 |
是否记录表格中行数 | 是 | 否 |
锁支持 | 表级锁定 | 行级锁,表级锁 |
哈希索引 | 不支持 | 支持 |
记录存储的顺序 | 按记录插入顺序保存 | 按主键大小有序插入 |
是否支持全文索引 | 支持 | 不支持 |
有一个表中其ID是自增键,当插入了17条语句以后,删除了三条语句 15,16,17,此时重启MySQL数据库之后再插入一条语句,这个语句的id是15 还是18。
当我们在使用到InnoDB做为存储引擎时候,若是说不进行重启的操作时候,这个id的值是18 但是若是进行了重新启动时候,对于InnoDB来说InnoDB表只把自增主键的最大ID记录到内存中,所以重启数据库或者对表OPTIMIZE操作,都会使最大ID丢失。所以这个时候id值就是15.
但是我们使用到MyISAM时候,那么这条记录的ID就是8。因为MylSAM表会把自增主键的最大ID记录到数据文件里面,重启MYSQL后,自增主键的最大ID也不会丢失。
对于两个存储引擎,那个使用 select count(*) 更快
对于MyISAM来说 ,速度会更快一点,因为对于MyISAM来说,内部维持了一个计数器,所以进行这个操作时候,时间复杂度是O(1),但是对于 InnoDB来说,需要进行计算,所以时间复杂度是O(n)。
如果没有特别的需求,使用默认的Innodb
即可。
MyISAM:以读写插入为主的应用程序,比如博客系统、新闻门户网站。
Innodb:更新(删除)操作频率也高,或者要保证数据的完整性;并发量高,支持事务和外键。比如OA自动化办公系统。
事务是一个不可分割的数据库操作序列,也是数据库并发控制的基本单位,其执行的结果必须使数据库从一种一致性状态变到另一种一致性状态。事务是逻辑上的一组操作,要么都执行,要么都不执行。
事务最经典也经常被拿出来说例子就是转账了。
假如小明要给小红转账1000元,这个转账会涉及到两个关键操作就是:将小明的余额减少1000元,将小红的余额增加1000元。万一在这两个操作之间突然出现错误比如银行系统崩溃,导致小明余额减少而小红的余额没有增加,这样就不对了。事务就是保证这两个关键操作要么都成功,要么都要失败。
按照严格的标准,只有同时满足ACID特性才是事务;但是在各大数据库厂商的实现中,真正满足ACID的事务少之又少。例如MySQL的NDB Cluster事务不满足持久性和隔离性;InnoDB默认事务隔离级别是可重复读,不满足隔离性;Oracle默认的事务隔离级别为READ COMMITTED,不满足隔离性……因此与其说ACID是事务必须满足的条件,不如说它们是衡量事务的四个维度。
概念:原子性是指一个事务是一个不可分割的工作单位,其中的操作要么都做,要么全部做。如果对于一个事务来说其中的sql语句执行失败,则已经执行的语句也必须要回滚,数据库退回到事务之前的状态。
实现原理:对介绍原子性之前,对于Mysql的事务日志来说,InnoDB提供了两种事务日志: redo log(重做日志)和 undo log(回滚日志)。其中的重做日志保证的是事物的持久性,回滚日志保证的是事务的原子性和隔离性实现的基础。
下面来具体介绍对于 原子性实现的关键: 当事务出现回滚时候能够撤销所有已经成功执行的sql语句。
InnoDB能够实现回滚的主要原因就是靠undo log: 当事务对数据库进行修改的时候,InnoDB会生成对应的undo log ;如果此时事务执行失败或者调动了 rollback,导致事务出现回滚情况,可以利用undo log中的信息将数据回滚到修改前的样子
undo log 属于一个逻辑日志,它用来记录的是sql执行相关的信息。对于一个insert语句在回滚时候会执行delete,相反也是如此。例如对于一个update在执行的时候,其生成的undolog 中会包含被修改的主键(以便知道修改了哪些行,修改了哪些列)以便在回滚时候能够使用这些记录的信息将数据还原到执行之前。
概念: 一致性就是MySQL 数据库最后追求的目标,数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性。例如在完成一次的转账之后,无论是成功还是失败,总金额不能够发生变化,在对一个表完成一次操作之后,对于任意字段的类型属性不能够被修改。
概念:与原子性,持久性侧重于研究事务本身不同,隔离性研究的是不同事务之间的相互影响。是指,事务内部的操作与其他的事务是隔离的,并发执行的各个事务之间是不能相互干扰。
隔离性追求的是并发情形下事务之间不会相互干扰,简单起见,我们仅考虑最简单的读操作和写操作(暂时不考虑带锁读等特殊操作),那么隔离性的探讨,主要可以分为两个方面:
概念: 事务一旦提交,他对数据库的改变就是永久的,接下来的其他操作或是故障都不应该对其造成影响。
实现原理:前面提到了InnoDB实现了两个事务日志,首先我们来聊一下redo log 存在的背景。
InnoDB作为MySQL的存储引擎,数据是存放在磁盘中的,但如果每次读写数据都需要磁盘IO,效率会很低。为此,InnoDB提供了缓存
(Buffer Pool),Buffer Pool中包含了磁盘中部分数据页的映射,作为访问数据库的缓冲:当从数据库读取数据时,会首先从Buffer Pool中读取,如果Buffer Pool中没有,则从磁盘读取后放入Buffer Pool;当向数据库写入数据时,会首先写入Buffer Pool,Buffer Pool中修改的数据会定期刷新到磁盘中(这一过程称为刷脏)。
Buffer Pool的使用大大提高了读写数据的效率,但是也带了新的问题:
如果MySQL宕机,而此时Buffer Pool中修改的数据还没有刷新到磁盘,就会导致数据的丢失,事务的持久性无法保证。
于是,redo log被引入来解决这个问题:当数据修改时,除了修改Buffer Pool中的数据,还会在redo log记录这次操作;当事务提交时,会调用fsync接口对redo log进行刷盘。如果MySQL宕机,重启时可以读取redo log中的数据,对数据库进行恢复。redo log采用的是WAL(Write-ahead logging,预写式日志),所有修改先写入日志,再更新到Buffer Pool,保证了数据不会因MySQL宕机而丢失,从而满足了持久性要求。
既然redo log也需要在事务提交时将日志写入磁盘,为什么它比直接将Buffer Pool中修改的数据写入磁盘(即刷脏)要快呢?主要有以下两方面的原因:
(1)刷脏是随机IO,因为每次修改的数据位置随机,但写redo log是追加操作,属于顺序IO。
(2)刷脏是以数据页(Page)为单位的,MySQL默认页大小是16KB,一个Page上一个小修改都要整页写入;而redo log中只包含真正需要写入的部分,无效IO大大减少。
我们知道,在MySQL中还存在binlog(二进制日志)也可以记录写操作并用于数据的恢复,但二者是有着根本的不同的:
(1)作用不同:redo log是用于crash recovery的,保证MySQL宕机也不会影响持久性;binlog是用于point-in-time recovery的,保证服务器可以基于时间点恢复数据,此外binlog还用于主从复制。
(2)层次不同:redo log是InnoDB存储引擎实现的,而binlog是MySQL的服务器层(可以参考文章前面对MySQL逻辑架构的介绍)实现的,同时支持InnoDB和其他存储引擎。
(3)内容不同:redo log是物理日志,内容基于磁盘的Page;binlog的内容是二进制的,根据binlog_format参数的不同,可能基于sql语句、基于数据本身或者二者的混合。
(4)写入时机不同:binlog在事务提交时写入;redo log的写入时机相对多元:
脏读:某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的。此时读的是未提交。
幻读:在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几列(Row)数据,而另一个事务却在此时插入了新的几列数据,先前的事务在接下来的查询中,就会发现有几列数据是它先前所没有的。读取到的行数不一样。
不可重复读: 在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新的原有的数据。两次读取到的值不一样。
为了达到事务的四大特性,数据库定义了4种不同的事务隔离级别,由低到高依次为Read uncommitted、Read committed、Repeatable read、Serializable,这四个级别可以逐个解决脏读、不可重复读、幻读这几类问题。
隔离级别 | 脏读 | 幻读 | 不可重复读 |
---|---|---|---|
读未提交 | √ | √ | √ |
读已提交 | × | √ | √ |
可重复读 | × | × | × |
可串行化 | × | × | × |
读未提交: 顾名思义就是可以读取到未提交的数据,就会导致,脏读,幻读,和不可重复读。
读已提交:允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
可重复读: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
可串行化: 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
注意:对于MySQL来说默认的隔离级别是可重复度,对于Oracle 来说默认的隔离级别就是 读已提交。
整数类型
包括TINYINT、SMALLINT、MEDIUMINT、INT、BIGINT,分别表示1字节、2字节、3字节、4字节、8字节整数。任何整数类型都可以加上UNSIGNED属性,表示数据是无符号的,即非负整数。
长度
:整数类型可以被指定长度,例如:INT(11)表示长度为11的INT类型。长度在大多数场景是没有意义的,它不会限制值的合法范围,只会影响显示字符的个数,而且需要和UNSIGNED ZEROFILL属性配合使用才有意义。
例子
,假定类型设定为INT(5),属性为UNSIGNED ZEROFILL,如果用户插入的数据为12的话,那么数据库实际存储数据为00012。
实数类型
包括FLOAT、DOUBLE、DECIMAL。
DECIMAL可以用于存储比BIGINT还大的整型,能存储精确的小数。
而FLOAT和DOUBLE是有取值范围的,并支持使用标准的浮点进行近似计算。
计算时FLOAT和DOUBLE相比DECIMAL效率更高一些,DECIMAL你可以理解成是用字符串进行处理。
字符串类型
包括VARCHAR、CHAR、TEXT、BLOB
VARCHAR用于存储可变长字符串,它比定长类型更节省空间。
VARCHAR使用额外1或2个字节存储字符串长度。列长度小于255字节时,使用1字节表示,否则使用2字节表示。
VARCHAR存储的内容超出设置的长度时,内容会被截断。
CHAR是定长的,根据定义的字符串长度分配足够的空间。
CHAR会根据需要使用空格进行填充方便比较。
CHAR适合存储很短的字符串,或者所有值都接近同一个长度。
CHAR存储的内容超出设置的长度时,内容同样会被截断。
枚举类型
把不重复的数据存储为一个预定义的集合。
有时可以使用ENUM代替常用的字符串类型。
ENUM存储非常紧凑,会把列表值压缩到一个或两个字节。
ENUM在内部存储时,其实存的是整数。
尽量避免使用数字作为ENUM枚举的常量,因为容易混乱。
排序是按照内部存储的整数
日期和时间类型
尽量使用timestamp,空间效率高于datetime,
用整数保存时间戳通常不方便处理。
如果需要存储微妙,可以使用bigint存储。
索引是一种特殊的文件(InnoDB数据表上的索引是表空间的一个组成部分),它们包含着对数据表里所有记录的引用指针。
索引是一种数据结构。数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用B树及其变种B+树。
更通俗的说,索引就相当于目录。为了方便查找书中的内容,通过对内容建立索引形成目录。索引是一个文件,它是要占据物理空间的。
主键索引: 数据列不允许出现重复字段,不允许为NULL值,并且对于一个表来说只能有一个主键。
唯一索引: 数据列不允许出现重复,但是允许为NULL值,一个表允许多个列创建唯一索引。
ALTER TABLE table_name ADD UNIQUE (column);
创建唯一索引。ALTER TABLE table_name ADD UNIQUE (column1,column2);
创建唯一组合索引。普通索引:基本的索引类型,没有唯一性的限制,允许为NULL值。
可以通过ALTER TABLE table_name ADD INDEX index_name (column);
创建普通索引
可以通过ALTER TABLE table_name ADD INDEX index_name(column1, column2, column3);
创建组合索引
全文索引:
ALTER TABLE table_name ADD FULLTEXT (column);
创建全文索引(目前只用MyISAM 支持全文索引)可以创建其他类型的索引,但是不能够创建全文索引。
create table text(
id int auto_increment PRIMARY key,
first_name varchar(20),
last_name varchar(20),
age int,
address text,
key name (first_name,last_name),// 联合索引
fulltext key (address),// 全局索引
UNIQUE key (id)// 唯一索引
);
完成之后使用explain执行计划查看:发现 type类型为 ref:表示此时索引的使用类型为:使用非唯一索引或非唯一索引前缀进行的查找。
可以来创建普通,UNIQUE,或者是PRIMIARY KEY 索引
其中 table_name是要增加索引的表名,column_list表示是对哪些列进行创建索引
可以直接对表增加普通索引或者UNIQUE索引
可利用ALTER TABLE或DROP INDEX语句来删除索引。类似于CREATE INDEX语句,DROP INDEX可以在ALTER TABLE内部作为一条语句处理,语法如下。
其中,前两条语句是等价的,删除掉table_name中的索引index_name。
第3条语句只在删除PRIMARY KEY索引时使用,因为一个表只可能有一个PRIMARY KEY索引,因此不需要指定索引名。如果没有创建PRIMARY KEY索引,但表具有一个或多个UNIQUE索引,则MySQL将删除第一个UNIQUE索引。
如果从表中删除了某列,则索引会受到影响。对于多列组合的索引,如果删除其中的某列,则该列也会从索引中删除。如果删除组成索引的所有列,则整个索引将被删除。
时间方面:创建索引和维护索引要耗费时间,具体地,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,会降低增/改/删的执行效率;
空间方面:索引需要占物理空间。
对于目前主流的数据结构就是B+树索引,与Hash索引,而我们经常使用的InnoDB存储引擎的默认索引实现为:B+树索引。对于哈希索引来说,底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景,建议选择BTree索引。
对于现在MySQL索引基本上都是B+树,现在基本上所说的也都是B+树,一般都称作是B树,但是对于B树和B+树来说差别还是很大。
B+tree性质:
1.)n棵子tree的节点包含n个关键字,不用来保存数据而是保存数据的索引。
2.)所有的叶子结点中包含了全部关键字的信息,及指向含这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。
3.)所有的非终端结点可以看成是索引部分,结点中仅含其子树中的最大(或最小)关键字。
4.)B+ 树中,数据对象的插入和删除仅在叶节点上进行。
5.)B+树有2个头指针,一个是树的根节点,一个是最小关键码的叶节点。
可以看到其实对于B+树来说所有的信息都存储在叶子节点,这个时候在进行数据库查询时候,进行磁盘的查询,可以进行更快的查询,因为对于每次的查询的量是固定的,但是对于非叶子节点不存储信息,所有能够遍历更多的非叶子节点,并且所有的叶子节点的查询的路径的长度都是相同的。
因为还是最开始的思想 减少磁盘的io的次数。因为尽可能的少减少磁盘的io的次数:
树的深度过大会造成磁盘IO频繁读写。根据磁盘查找存取的次数往往由树的高度所决定,所以,只要我们通过某种较好的树结构减少树的结构尽量减少树的高度
类似于数据结构中简单实现的HASH表(散列表)一样,当我们在mysql中用哈希索引时,主要就是通过Hash算法(常见的Hash算法有直接定址法、平方取中法、折叠法、除数取余法、随机数法),将数据库字段数据转换成定长的Hash值,与这条数据的行指针一并存入Hash表的对应位置;如果发生Hash碰撞(两个不同关键字的Hash值相同),则在对应Hash键下以链表形式存储。当然这只是简略模拟图。
对于这个问题算是单独独立出来,来进行深刻的刨析以及底层的原理。
因为文件很大的时候,不可能将所有的信息记录都保存在内存中,需要一部分保存在磁盘上,但是当访问时候,访问磁盘需要进行查找,索引的目的就是能够快速的进行查找工作,索引索引的结构很关键,要尽量得减少在查找过程中磁盘I/O的存取次数,对于B+树来说 所有的数据域都保存在叶子节点上,并且没层的数据会比较多,为了就是能够分得更少的层次,在进行优化查找时候,每个节点到叶子节点的高度都是相同的 就是将所有的叶子节点使用指针串起来,这样就能够使用遍历叶子节点来获取数据。非叶子节点只保存索引,叶子节点才会保存数据。
为什么能够加快访访问速度
首先明白一点就是对于索引来说是为了给我们加快数据查询时候的速度,当我们在查询数据的时候难免会对数据进行检索操作,这个时候若是不走索引时候,where语句后面的检索就会对表进行遍历的处理,所以一般对于经常出现在where,等判断条件的字段上面添加索引。
例如如下操作:我们在没有创建索引时候,可以看到没有走索引信息,走的是全文查询 type的类型是ALL。
(具体的explain 执行计划会在下面讲到)
现在为tags 来增添一个索引表示添加一个普通的索引:
然后再来查询一遍,可以看到type的类型发生了改变,同时可能出现索引的key也发生了改变。
当然在当前数据量比较小的情况下,暂时不会出现什么数据库访问压力问题,但是若是在数据库字段比较多时候查询就会出现查询时间过长的问题。
order by
当我们使用order by将查询结果按照某个字段排序时,如果该字段没有建立索引,那么执行计划会将查询出的所有数据使用外部排序(将数据从硬盘分批读取到内存使用内部排序,最后合并排序结果),这个操作是很影响性能的,因为需要将查询涉及到的所有数据从磁盘中读到内存(如果单条数据过大或者数据量过多都会降低效率),更无论读到内存之后的排序了。
但是如果我们对该字段建立索引alter table 表名 add index(字段名),那么由于索引本身是有序的,因此直接按照索引的顺序和映射关系逐条取出数据即可。而且如果分页的,那么只用取出索引表某个范围内的索引对应的数据,而不用像上述那取出所有数据进行排序再返回某个范围内的数据。(从磁盘取数据是最影响性能的)
join
对join语句匹配关系(on)涉及的字段建立索引能够提高效率
对于这个问题,在面试时候会问到:“现在你已经为你表的某个字段创建了一个索引,如何来判断自己的这个索引有没有被使用到,或者说查看哪个字段能够判断索引的执行效率”
首先对我们需要执行的sql语句使用到explain执行计划,如下图所示。
下面来具体刨析每个字段以及字段属性的具体值所代表的含义:
id:表示查询中各个子查询执行时候的执行顺序。
select_type: 表示子查询的查询类型,下面列举常见的类型
id | select_type | 具体描述 |
---|---|---|
1 | SIMPLE | 不包含任何子查询或union等查询 |
2 | PRIMARY | 包含子查询最外层查询就显示为 PRIMARY |
3 | SUBQUERY | 在select或 where字句中包含的查询 |
4 | DERIVED | from字句中包含的查询 |
5 | UNION | 出现在union后的查询语句中 |
table: 表示在当前执行计划中走的是哪一个表。
type: 表示在此条sql语句中走的索引的类型,一般也都是看此字段,来判断此条sql语句执行的效率高不高。
possible_keys: 可能使用的索引,注意不一定会使用。查询涉及到的字段上若存在索引,则该索引将被列出来。当该列为 NULL时就要考虑当前的SQL是否需要优化了。
key : 显示MySQL在查询中实际使用的索引,若没有使用索引,显示为NULL。
key_length: 索引长度
ref: 表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值。
rows: 返回估算的结果集数目,并不是一个准确的值。
extra 的信息非常丰富,常见的有:
注意:在上面的执行计划中我们查看完成之后主要是看**type**属性来判断当前的sql语句是否需要进行优化的处理。
至少要达到 range 级别,要求是ref级别,如果能够达到consts是最好的情况。
说明:
1) consts 单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据。
2) ref 指的是使用普通的索引(normal index)。
3) range 对索引进行范围检索。
通过建立索引并不一定一定能够对性能进行一个提升,在我们通常的情况下,在建立索引的基础上,一个查询走索引确实能够比走全表查询消耗更少的性能,但是我们也需要注意有些情况:
索引是真实存在,也需要消耗一定的存储空间,也需要进行定期的维护处理,每当有记录在表中增减或索引列被修改时,索引本身也会被修改。 这意味着每条记录的INSERT,DELETE,UPDATE将为此多付出4,5 次的磁盘I/O。 因为索引需要额外的存储空间和处理,那些不必要的索引反而会使查询反应时间变慢。使用索引查询不一定能提高查询性能,索引范围查询(INDEX RANGE SCAN)适用于两种情况:
MySQL可以使用多个字段同时建立一个索引,叫做联合索引。在联合索引中,如果想要命中索引,需要按照建立索引时的字段顺序挨个使用,否则无法命中索引。
具体原因为:
MySQL使用索引时需要索引有序,假设现在建立了"name,age,school"的联合索引,那么索引的排序为: 先按照name排序,如果name相同,则按照age排序,如果age的值也相等,则按照school进行排序。
当进行查询时,此时索引仅仅按照name严格有序,因此必须首先使用name字段进行等值查询,之后对于匹配到的列而言,其按照age字段严格有序,此时可以使用age字段用做索引查找,以此类推。因此在建立联合索引的时候应该注意索引列的顺序,一般情况下,将查询需求频繁或者字段选择性高的列放在前面。此外可以根据特例的查询或者表结构进行单独的调整。
mysql可以在使用索引来直接获取列的数据,这样就不需要来读取数据行,如果索引的叶子节点中以及包含了要查询的数据,那么还有什么必要再回表查询呢,如果一个索引包含(或者说是覆盖)所有需要查询的字段的值,我们就称为“覆盖索引”。
有什么好处:
重复索引指的是在相同列上按照相同的顺序创建的相同类型的索引。
举个例子
create table test(
ID int NOT NULL primiary key,
A int not null,
B int not null,
unique(ID),
index(ID),
)engine=InnoDB;
此时我们看到的是,一个用户想要创建一个主键,加上唯一限制,然后加上索引以供查询使用。事实上,MySql的唯一限制和主键限制都是通过索引实现的,因此,上面的写法实际上在相同的列上创建了三个重复的索引。通常没有这样做的理由,除非是在同一列上创建不同类型的索引来满足不同的查询需求。
冗余索引和重复索引有所不同,如果创建了索引(A,B),再创建索引 (A),就是冗余索引,因为这只是我们前面创建索引的一个前缀索引。因此索引(A,B)也可以当做索引(A)来使用,注意这里我们说的冗余只是对B-Tree来说的。但是如果再创建索引(B,A)却不能被视为冗余索引,同时索引(B)也不是,因为B不是索引(A,B)的最左前缀列。另外,其他不同类型的索引(例如哈希索引,或者是全文索引)也不会是B-Tree索引的冗余索引,而无论覆盖的索引列是什么。
在日常的工作中,冗余索引通常发生在为表添加新索引的时候,例如,有人可能会增加一个新的索引(A,B)而不是扩展已有的索引(A),还有一种情况就是将一个索引扩展为(A,ID),其中ID是主键,对于InnoDB来说主键列已经包含在二级索引中,所以这样的情况也是冗余的。
对于这个题目,切实在面试中遇到中,当时在问我索引的类型等,然后就谈论到“那你知道什么是回表查询?”
对于这个问题想要理解需要先明白对于Mysql索引大体分为两类:一个聚集索引,一个是普通索引:
InnoDB聚集索引的叶子节点存储行记录,因此InnoDB必须要有且只有一个聚集索引。
1.如果表定义了PK(Primary Key,主键),那么PK就是聚集索引。
2.如果表没有定义PK,则第一个NOT NULL UNIQUE的列就是聚集索引。
3.否则InnoDB会另外创建一个隐藏的ROWID作为聚集索引。
就是说 当我们在使用非聚簇索引(对于那些非主键进行索引值时候)找到了记录的行,但是还是需要根据主键再次找到数据快里面对应的数据。
一个是一个表的ID是主键 我们查询
select * from stu where id=1;
是直接查询到的
但是还有一个name不是主键 有普通的索引
查询时候就需要进行一个回表的操作。
不一定,这涉及到查询语句所要求的字段是否全部命中了索引,如果全部命中了索引,那么就不必再进行回表查询。
举个简单的例子,假设我们在员工表的年龄上建立了索引,那么当进行select age from employee where age < 20的查询时,在索引的叶子节点上,已经包含了age信息,不会再次进行回表查询。
表级锁:开销小,加锁快,不会出现死锁。锁定粒度大,发送锁冲突的概率比较高,并发处理效果较低。
行级锁: 开销大,加锁慢,会出现死锁,锁定粒度较大,发生锁冲突的概率会小一点,并发处理的效果高。
死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方的资源,从而导致恶性循环的现象。
常见的解决死锁的方法
1、如果不同程序会并发存取多个表,尽量约定以相同的顺序访问表,可以大大降低死锁机会。
2、在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁产生概率;
3、对于非常容易产生死锁的业务部分,可以尝试使用升级锁定颗粒度,通过表级锁定来减少死锁产生的概率;
数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性。乐观并发控制(乐观锁)和悲观并发控制(悲观锁)是并发控制主要采用的技术手段。
悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。在查询完数据的时候就把事务锁起来,直到提交事务。实现方式:使用数据库中的锁机制
乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。在修改数据的时候把事务锁起来,通过version的方式来进行锁定。实现方式:乐一般会使用版本号机制或CAS算法实现。
两种锁的使用场景
从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。
但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。
答:InnoDB是基于索引来完成行锁
例: select * from tab_with_index where id = 1 for update;
for update 可以根据条件来完成行锁锁定,并且 id 是有索引键的列,如果 id 不是索引键那么InnoDB将完成表锁,并发将无从谈起
对于这个题目也是在面试中切实尽量过
例如我们有如下sql语句
select * from user where id=6
就是查询一个id为6的用户的信息执行流程是
客户端-》Server层-》 引擎层。
其中server层包括: 连接器,分析器,查询器,优化器,执行器
引擎层: 就是底层的引擎 主要负责是存储数据,提供读写的接口
就是使用客户端或者命令行时候输入的用户名与密码进行的连接。在你连接成功以后,此用户操作数据的权限判断逻辑都将依赖查询到的权限,此时修改用户的权限也是无用,必须重新登录才行。这里说明一下对于客户端连接到的数据库而言,如果连接一直处于空闲的状态,那么到了一定的时间限制就会断开连接,这个连接的默认时间是8个小时。
对于连接成功以后,执行SQL时候会先行查询缓存,如果有相对应的数据就会直接返回给客户端进行显示,后面的步骤就不会再执行下去。
它的原理怎样的呢?请接着往下看,一条查询sql的首次执行完成后,会把sql语句作为key,把查询出来的数据作为value放入到缓存中,如果后面再有相同的查询,那么直接从缓存中取值便可。
看到这里也许你们会想缓存这么好用,那以后要多用缓存。别急,请接着往下看。查询缓存用起来确实好用,但是它有一个弊端,那就是当这个表做了更新操作时,那么此表的缓存将会全部清空。也许当你辛辛苦苦缓存起来的数据,还没来得及用时就可能被一条update语句给全部干掉。所以如果更新比较频繁的表是不适合使用缓存的,如果是某些配置表倒是比较适合缓存的使用。
当前面的不能够缓存命中的时候,sql就会开始真正执行流程,首先是分析器,包括语法分析,词法分析。
词法分析: 就是对sql语句中非每一个单词都逐个的做分析,比如 对于 select来说 就会识别出来时查询语句。user 就会分析出来时 表user 然后对应的信息一一对应。
语法分析就会分析: 写的这条语句是否满足sql的语法,若是你对于 where语句以后没有给定的限制信息就会报错,对于 select后来没有选中值也会报错等
对于 执行完分析器以后,就能够知道所执行的是什么类型的语句 是查询,还是插入等,进行优化。例如 使用表的哪个索引;或者是进行表的关联,决定关联的顺序等等问题。
当完成了优化以后,就表示此sql语句选择好了一个比较完拖的方案,可以进行执行阶段。首先要先检查你进行的操作是否有对应的表的权限。如果有了权限以后,执行器会根据定义的表的引擎,来执行引擎所对应的接口信息 mysql5.5之后存储引擎是 InnoDB。
其大致的流程如下:
如果是 id 是有索引的,第一次调用的是“取满足条件的第一行”这个接口,之后循环取“满足条件的下一行”这个接口,基本和上面差不多。这些接口都是引擎中已经定义好的。至此 一条 sql 便执行完成。
首先在实现主从复制之前,先对一些基础的术语有一个认识
DDL(Data Definition Languages)语句:即数据库定义语句,用来创建数据库中的表、索引、视图、存储过程、触发器等,常用的语句关键字有:CREATE,ALTER,DROP,TRUNCATE,COMMENT,RENAME。
DML(Data Manipulation Language)语句:即数据操纵语句,用来查询、添加、更新、删除等,常用的语句关键字有:SELECT,INSERT,UPDATE,DELETE,MERGE,CALL,EXPLAIN PLAN,LOCK TABLE,包括通用性的增删改查。
DCL(Data Control Language)语句:即数据控制语句,用于授权/撤销数据库及其字段的权限(DCL is short name of Data Control Language which includes commands such as GRANT and mostly concerned with rights, permissions and other controls of the database system.)。常用的语句关键字有:GRANT,REVOKE。TCL(Transaction Control Language)语句:事务控制语句,用于控制事务,常用的语句关键字有:COMMIT,ROLLBACK,SAVEPOINT,SET TRANSACTION。
因为对于MySQL来说性能是完完全全有限的,对于单点数据的读取的性能也是有很严重的上限要求。所以我们一般都是对主数据库进行写入的操作,对从数据库进行信息的读取。
1)、mysql主从复制用途
●实时灾备,用于故障切换
●读写分离,提供查询服务
●备份,避免影响业务
2)、主从部署必要条件:
●主库开启binlog日志(设置log-bin参数)
●主从server-id不同
●从库服务器能连通主库
实现的三种形式:
binlog 的记录模式:
缺点: 就是 由于只是记录下来sql语句 为了能够让sql语句正常的在 Slave上运行,还必须记录每条语句在执行的过程中一些相关的新。以确保得到与 主数据库相同的结果
row:
把每一条数据的变化写入到binlog中,不记录sql语句的上下文信息,仅保存哪条记录被修改。 就是会清楚记录下来每一行的变化,但是这样回产生大量的日志,例如一个更改表的语句,就会涉及到很多条的语句都会进行修改,就会导致比较冗余
mixed statement与row的混合。
Mysql决定何时写statement格式的binlog, 何时写row格式的binlog。 将两则的混合起来,起到最少的记录行信息
首先看一张图片:
需要三个线程来完成的,在从端有两个线程,sql线程与 I/O线程。
主端有 一个 I/O线程。在实现主从复制的时候 ,首先会开启 Master端的 binLog记录功能 因为整个的复制流程就是 Slave从Master端获取到 binlog日志,然后再Slave上以相同的顺序执行获取到的binlog日志中的记录中的各种的SQL操作。
主从复制是异步的逻辑的SQL语句级的复制
复制时,主库有一个I/O线程,从库有两个线程,I/O和SQL线程
实现主从复制的必要条件是主库要开启记录binlog功能
作为复制的所有Mysql节点的server-id都不能相同
binlog文件只记录对数据库有更改的SQL语句(来自主库内容的变更),不记录任何查询(select,show)语句
1).
MySQL数据库主从同步延迟原理mysql主从同步原理:主库针对写操作,顺序写binlog,从库单线程去主库顺序读”写操作的binlog”,从库取到binlog在本地原样执行(随机写),来保证主从数据逻辑上一致。
mysql的主从复制都是单线程的操作,主库对所有DDL和DML产生binlog,binlog是顺序写,所以效率很高,slave的Slave_IO_Running线程到主库取日志,效率比较高,下一步,问题来了,slave的Slave_SQL_Running线程将主库的DDL和DML操作在slave实施。DML和DDL的IO操作是随即的,不是顺序的,成本高很多,还可能可slave上的其他查询产生lock争用,由于Slave_SQL_Running也是单线程的,所以一个DDL卡主了,需要执行10分钟,那么所有之后的DDL会等待这个DDL执行完才会继续执行,这就导致了延时。有朋友会问:“主库上那个相同的DDL也需要执行10分,为什么slave会延时?”,答案是master可以并发,Slave_SQL_Running线程却不可以。
2) .
MySQL数据库主从同步延迟是怎么产生的?当主库的TPS并发较高时,产生的DDL数量超过slave一个sql线程所能承受的范围,那么延时就产生了,当然还有就是可能与slave的大型query语句产生了锁等待。首要原因:数据库在业务上读写压力太大,CPU计算负荷大,网卡负荷大,硬盘随机IO太高次要原因:读写binlog带来的性能影响,网络传输延迟。
在面试过程中切实遇到。
逻辑上:
对于Mysql来说默认上就是异步的,主库在执行完客户端提交的事务后会立即将结果返给给客户端,并不关心从库是否已经接收并处理,这样就会有一个问题,主如果crash掉了,此时主上已经提交的事务可能并没有传到从库上,如果此时,强行将从提升为主,可能导致新主上的数据不完整。
技术上:
主库将事务 Binlog 事件写入到 Binlog 文件中,此时主库只会通知一下 Dump 线程发送这些新的 Binlog,然后主库就会继续处理提交操作,而此时不会保证这些 Binlog 传到任何一个从库节点上。
逻辑上:
指当主库执行完一个事务,所有的从库都执行了该事务才返回给客户端。因为需要等待所有从库执行完该事务才能返回,所以全同步复制的性能必然会收到严重的影响。
技术上:
当主库提交事务之后,所有的从库节点必须收到、APPLY并且提交这些事务,然后主库线程才能继续做后续操作。但缺点是,主库完成一个事务的时间会被拉长,性能降低。
逻辑上:
前面提到的 异步复制,是不查看从库是否已经完成了对于主库的赋值,全同步复制是,接收完所有的从库复制信息以后才会返回给客户端。但是对于 半同步复制来说,主库在执行完客户端提交的事务之后并不会立刻返回给客户端,而是等待至少一个从库接收到并写到Relay
olg中才返回给客户端,相当于一个简约版的异步复制,提高了一定的安全性。但是也造成了一定程度的延迟情况,这个延迟最少是一个TCP/IP 往返时间,所以来说 版同步复制最好在低延迟的网络中使用到。
select cno,count(sno) from stu group by cno;
表示就是说 以cno进行分类处理 对于 cno来说有几个分类就列举出来几行 同时计算出 以cno为相同时候的count sno的值。
就是表的相连接以及 聚合函数的使用 对于两个表时候,我们使用到直接逗号的方法进行连接即可 但是对于相同的字段要保持前缀的存在。
举个栗子:
查询选修了3门以上课程,且所有课程成绩都高于60分的学生学号及课程数
select sno,count(cno)
from stu
where grade>60
group by sno having count(cno)>3;
举个栗子
等值连接:ON A.id=B.id
不等值连接ON A.id > B.id
自连接 就是说对于同一个表自己和自己相连接
select e.emp,d.emp from employ e
inner join employ b where e.bossid==b.id;
有两个表,一个是X表,其中有三个字段ABC,一个是Y表有两个字段CD,两张表之间只有c字段是共同的字段,来进行不同的操作,验证我们的各个连接的情况。如下图所示:
X表:
A | B | C |
---|---|---|
a1 | b1 | c1 |
a2 | b2 | c2 |
a3 | b3 | c3 |
Y表
C | D |
---|---|
c1 | d1 |
c2 | d2 |
c4 | d3 |
交叉连接(笛卡尔积)
select x.*,y.* from r,s;// 最后应该是X表的行乘于Y表的行
A | B | C | C | D |
---|---|---|---|---|
a1 | b1 | c1 | c1 | d1 |
a2 a3 | ||||
a1 | b1 | c1 | c2 | d2 |
a2 a3 | ||||
a1 | b1 | c1 | c4 | d3 |
a2 a3 |
内连接
select x.*,y.* from x inner join y on x.c=y.c;// 要求要完完全全相等
A | B | C | C | D |
---|---|---|---|---|
a1 | b1 | c1 | c1 | d1 |
a2 | b2 | c2 | c2 | d2 |
左连接
select x.*,y.* from x left join y on x.c=y.c;// 对于左连接出现的情况是先将左边的表进行全查询处理,与右边表有相对应位置时候显示对应的值,若是没有相等的值,该列的值为空。
A | B | C | C | D |
---|---|---|---|---|
a1 | b1 | c1 | c1 | d1 |
a2 | b2 | c2 | c2 | d2 |
a3 | b3 | c3 |
右连接
select r.*,s.* from r right join s on r.c=s.c
A | B | C | C | D |
---|---|---|---|---|
a1 | b1 | c1 | c1 | d1 |
a2 | b2 | c2 | c2 | d2 |
c4 | d3 |
select r.*,s.* from r full join s on r.c=s.c
A | B | C | C | D |
---|---|---|---|---|
a1 | b1 | c1 | c1 | d1 |
a2 | b2 | c2 | c2 | d2 |
a3 | b3 | c3 | ||
c4 | d3 |
子查询是单行单列的情况:结果集是一个值,父查询使用:=、 <、 > 等运算符
-- 查询工资最高的员工是谁?
select * from employee where salary=(select max(salary) from employee);
子查询是多行单列的情况:结果集类似于一个数组,父查询使用:in 运算符
select * from employee where salary in(select salary from employee group by card_id)
子查询是多行多列的情况:结果集类似于一张虚拟表,不能用于where条件,用于select子句中做为子表
-- 1) 查询出2011年以后入职的员工信息
-- 2) 查询所有的部门信息,与上面的虚拟表中的信息比对,找出所有部门ID相等的员工。
select * from dept d, (select * from employee where join_date > '2011-1-1') e where e.dept_id = d.id;
-- 使用表连接:
select d.*, e.* from dept d inner join employee e on d.id = e.dept_id where e.join_date > '2011-1-1'
在学习 from 子查询之前,需要理解一个概念:查询结果集在结构上可以当成表看,那就可以当成临时表对他进行再次查询:
首先执行下面这句语句以后,能得到一个结果,其中每个栏目的第一行都是 goods_id 最大的行:
select goods_id,cat_id,goods_name from goods order by cat_id asc,goods_id desc;
假设存在这张表,表名叫 tmp ,那么:
select * from (select goods_id,cat_id,goods_name from goods order by cat_id asc,goods_id desc) as tmp group by cat_id;
exists 型子查询是指外层 sql 的结果,拿到内层 sql 去测试,如果内层 sql 成立,则该行取出。学习之前先建一张表:
create table category(
-> cat_id int auto_increment primary key,
-> cat_name varchar(20) not null default ''
-> )engine myisam;
向里面插入原来 ecshop.ecs_category 中的部分数据:
insert into test.category select cat_id,cat_name from ecshop.ecs_category;
那么假设某个栏目的 cat_id 为 N,则 select * from goods where cat_id = N
如果能取出,则说明该栏目下有商品:
select cat_id,cat_name from category where exists (select * from goods where goods.cat_id=category.cat_id);
可以理解为,先把第一个 cat_id=1 取出来,带入到内层,那么内层就变成了:
select * from goods where goods.cat_id=1;
查询发现没有对应的值,返回 empty ,那么category 中 cat_id=1 这一行就不要了。接下来看 cat_id=2,代入,发现可以取出货物 goods 来,那么 category 中 cat_id=2 这一行就取出来
在前面讲到和学习到in 和exists都是在子查询时候来使用达到更高的效率,那两者之间的区别是什么呢?
首先就是说 对于数据比较多的情况下 crud 会导致比较低情况,这个时候就可以
就是说 将一个表差分为两个表,同时拥有主键。例如,用户表中既有用户的登录信息又有用户的基本信息,可以将用户表拆分成两个单独的表,甚至放到单独的库做分库。
优点就是说: 可以使得行数据变小,在查询的时候减少io的次数此外,垂直分区可以简化表的结构,易于维护。
垂直拆分的缺点: 主键会出现冗余,需要管理冗余列,并会引起Join操作,可以通过在应用层进行Join来解决。此外,垂直分区会让事务变得更加复杂;
把主键和一些列放在一个表,然后把主键和另外的列放在另一个表中
保持数据表结构不变,通过某种策略存储数据分片。这样每一片数据分散到不同的表或者库中,达到了分布式的目的。 水平拆分可以支撑非常大的数据量。
水平拆分是指数据表行的拆分,表的行数超过200万行时,就会变慢,这时可以把一张的表的数据拆成多张表来存放。举个例子:我们可以将用户信息表拆分成多个用户信息表,这样就可以避免单一表数据量过大对性能造成影响。
表很大,分割后可以降低在查询时需要读的数据和索引的页数,同时也降低了索引的层数,提高查询次数
可以结合explain 执行计划来具体查看
SELECT * FORM A WHERE ID IN(SELECT ID FROM B);
SELECT * FROM A WHERE ESISTS(SELECT 1 FROM B WHERE ID =A.ID AND COLOR= A.COLOR);
为了提高复杂SQL语句的复用性和表操作的安全性,MySQL数据库管理系统提供了视图特性。所谓视图,本质上是一种虚拟表,在物理上是不存在的,其内容与真实的表相似,包含一系列带有名称的列和行数据。但是,视图并不在数据库中以储存的数据值形式存在。行和列数据来自定义视图的查询所引用基本表,并且在具体引用视图时动态生成。
视图使开发者只关心感兴趣的某些特定数据和所负责的特定任务,只能看到视图中所定义的数据,而不是视图所引用表中的数据,从而提高了数据库中数据的安全性。
视图根本用途:简化sql查询,提高开发效率。如果说还有另外一个用途那就是兼容老的表结构。
下面是视图的常见使用场景:
重用SQL语句;
简化复杂的SQL操作。在编写查询后,可以方便的重用它而不必知道它的基本查询细节;
使用表的组成部分而不是整个表;
保护数据。可以给用户授予表的特定部分的访问权限而不是整个表的访问权限;
更改数据格式和表示。视图可返回与底层表的表示和格式不同的数据。
限制数据访问。视图在本质上其实就是一条select语句,所以当在 访问视图的时候,只能访问到Select语句所对应的列,对基表中的其他列起到了保护的作用。
性能。数据库必须把视图的查询转化成对基本表的查询,如果这个视图是由一个复杂的多表查询所定义,那么,即使是视图的一个简单查询,数据库也把它变成一个复杂的结合体,需要花费一定的时间。
修改限制。当用户试图修改视图的某些行时,数据库必须把它转化为对基本表的某些行的修改。事实上,当从视图中插入或者删除时,情况也是这样。对于简单视图来说,这是很方便的,但是,对于比较复杂的视图,可能是不可修改的
这些视图有如下特征:1.有UNIQUE等集合操作符的视图。2.有GROUP BY子句的视图。3.有诸如AVG\SUM\MAX等聚合函数的视图。 4.使用DISTINCT关键字的视图。5.连接表的视图(其中有些例外)
触发器是用户定义在关系表上的一类由事件驱动的特殊的存储过程。触发器是指一段代码,当触发某个事件时,自动执行这些代码。
使用场景
在MySQL数据库中有如下六种触发器:
最多存放50个字符,varchar(50)和(200)存储hello所占空间一样,但后者在排序时会消耗更多内存,因为order by col采用fixed_length计算col长度(memory引擎也一样)。在早期 MySQL 版本中, 50 代表字节数,现在代表字符数。
是指显示字符的长度。20表示最大显示宽度为20,但仍占4字节存储,存储范围不变;
不影响内部存储,只是影响带 zerofill 定义的 int 时,前面补多少个 0,易于报表展示
对大多数应用没有意义,只是规定一些工具用来显示字符的个数;int(1)和int(20)存储和计算均一样;
int(10)的10表示显示的数据的长度,不是存储数据的大小;chart(10)和varchar(10)的10表示存储数据的大小,即表示存储多少个字符。
int(10) 10位的数据长度 9999999999,占32个字节,int型4位
char(10) 10位固定字符串,不足补空格 最多10个字符
varchar(10) 10位可变字符串,不足补空格 最多10个字符
char(10)表示存储定长的10个字符,不足10个就用空格补齐,占用更多的存储空间
varchar(10)表示存储10个变长的字符,存储多少个就是多少个,空格也按一个字符存储,这一点是和char(10)的空格不同的,char(10)的空格表示占位不算一个字符
select class from courses group by class
having count( distinct student) >= 5;
Employee 表包含所有员工信息,每个员工有其对应的 Id, salary 和 department Id。
Department 表包含公司所有部门的信息。
编写一个 SQL 查询,找出每个部门工资最高的员工。例如,根据上述给定的表格,Max 在 IT 部门有最高工资,Henry 在 Sales 部门有最高工资。
SELECT
Department.NAME AS Department,
Employee.NAME AS Employee,
Salary
FROM
Employee,
Department
WHERE
Employee.DepartmentId = Department.Id
AND ( Employee.DepartmentId, Salary )
IN (SELECT DepartmentId, max( Salary )
FROM Employee
GROUP BY DepartmentId )
编写一个 SQL 查询来实现分数排名。
如果两个分数相同,则两个分数排名(Rank)相同。请注意,平分后的下一个名次应该是下一个连续的整数值。换句话说,名次之间不应该有“间隔”。
例如,根据上述给定的 Scores 表,你的查询应该返回(按分数从高到低排列):
select a.Score as Score,
(select count(distinct b.Score) from Scores b where b.Score >= a.Score) as `Rank`
from Scores a
order by a.Score DESC