面试记录-Mysql

207 阅读44分钟

一、Mysql架构

Mysql与其他db相比,mysql有点与众不同,它的架构可以在多种不同用场景中应用并发挥良好作用。主要体现在存储引擎上,插件式的存储引擎架构将查询处理和其他的系统任务以及数据的存储提取相分离。

这种架构可以根据业务的需求和实际需要选择合适的存储引擎。

连接层:最上层是一些客户端和连接服务。主要完成一些类似于连接处理,授权认证,以及相关的安全方案。在该层上引入了线程池的概念,为通过认证安全接入的客户端提供线程。同样在该层上可以实现

基于SSL的安全链接。

服务层:第二层服务层,主要完成大部分的核心功能,包括查询解析,分析,优化,缓存,以及所有的内置函数,所有跨存储引擎的功能也都在这一层实现,包括存储过程,触发器,视图等。

引擎层:第三层存储引擎层,存储引擎真正负责了mysql中数据的存储和提取,服务器通过API与存储引擎进行通信。不同的存储引擎具有的哦功能不同。

存储层:第四层为数据存储层,主要是将数据存储在运行于该设备的文件系统上,并完成与存储引擎交互。

Mysql的执行流程:客户端请求-->连接器(验证用户身份,给予权限)-->查询缓存(存在查询则返回,如果不存在则执行后续操作)-->分析器(对sql进行词法分析和语法分析)-->优化器(主要对执行的sql

优化选择最优的执行方案方法)-->执行器(执行时会先看用户是否有执行权限,有才去使用这个引擎提供的接口)-->去引擎层返回数据(如果开启查询缓存则会缓存查询结果)。

二、存储引擎

存储引擎是Mysql的组件,用于处理不同表类型的sql操作。一个数据库中多个表可以使用不同的引擎以满足各种性能和实际需求。

查看存储引擎:SHOW ENGINES;

设置存储引擎:ENGINE=INNODB;

存储引擎对比:

Innodb存储引擎,当前mysql主流的引擎,Innodb存储引擎支持事务,支持行锁,支持非锁级读,支持外键。

Myisam存储引擎不支持事务,不支持行级锁,支持表锁(效率低),支持全文索引,最大的缺陷是奔溃后无法恢复。

存储文件对比:

Myisam,.frm文件存储定义表结构的信息。.MYD文件是Myisam专用,存储Myisam表数据。.MYI文件是Myisam专用,存储Myisam表的索引信息。

Innodb,.frm文件存储定义表结构的信息。.ibd文件(独享表空间存储方式,每个表一个文件)或者.ibdata文件(共享表空间存储方式,所有表共同使用一个文件),这两种都是存放Innodb数据的文件。

面试这么回答

说说MySQL有哪些存储引擎?都有哪些区别?

1.Innodb支持事务,Myisam不支持事务。这是Mysql将默认存储引擎从Myisam变为Innodb的重要原因之一。

2.Innodb支持外键,Myisam不支持。对一个包含外键的Innodb表转为Myisam会失败。

3.Innodb是聚簇索引,而Myisam是非聚簇索引。聚簇索引的文件存放在主键索引的叶子节点上,因此Innodb必须要有主键,通过主键索引效率很高。但是辅助索引需要两次查询,先查询到主键

在通过主键查到数据。因此,主键不应该太大,主键太大,其他索引也会很大。而Myisam是非聚簇索引,数据文件是分离的,索引保存的是数据文件的指针。主索引和辅助索引是独立的。

4.Innodb不保存表的具体行数,执行select count(*) from table时需要全表扫描。而Myisam是用一个变量保存了整个表的行数。

5.Innodb最小的锁粒度是行级锁,Myisam最小的锁粒度是表锁。一个更新语句会锁住整张表,导致其他查询和更新都会阻塞,因此并发访问受限。这又是mysql将默认存储引擎转为Innodb的原因之一。

一张表,里面有ID自增主键,当insert了17条记录之后,删除了第15,16,17条记录,再把Mysql重启,再insert一条记录,这条记录的ID是18还是15 ?

如果表的存储引擎是Myisam,那么就是18。因为Myisam表会把自增主键的最大Id,记录到数据文件中,重启Mysql自增主键的最大Id也不会丢失。

如果表的存储引擎是Innodb,那么就是15。因为Innodb表只是吧自增主键的最大Id存储在内存中,所以重启数据库或者对表进行OPTION操作,都会导致最大Id丢失。

哪个存储引擎执行 select count(*) 更快,为什么?

Myisam更快,因为Myisam内部维护了一个计数器,可以直接读取。在Myisam存储引擎中,把表的总行数存在磁盘中,select count(*)时,会直接返回。Innodb中是全表扫描,累加,最后返回总数量。

Innodb中count(*)执行的时候,是全表扫描,所以表的数据越大,语句执行越耗时,越慢。为什么Innobd不像Myisam存储一个变量在磁盘上呢?这根Innodb的事务特性有关,由于多版本并发控制(MVVC)

的原因,Innodb“应该返回多少行”也是不确定的。

三、数据类型

具体详见对比图

整数类型:BIT,BOOLEAN,TINYINT,SMALLINT,MEDIUMINT,INT,BIGINT.

浮点类型:FLOAT,DOUBLE,DECIMAL

字符串类型:CHAR,VARCHAR,TINYTEXT,TEXT,MEDIUMTEXT,LONGTEXT,TINYBLOB,BLOB,MEDIUMBLOB,LONGBLOB.

日期类型:Date,DateTime,TIMeSTamp,Time,YEAR.

其他数据类型:BINARY,VARBINARY,ENUM,SET,Geometry,Point,MultiPoint,LineString,MultiLineString,Polygon,GeometryCollection等。

CHAR和VARCHAR的区别?

Char是固定的,Varchar长度可变:char(n)和varchar(n)中n代表字符串的个数,并不代表字节数,比如char(30)就可以存30个字符。

存储时char不管实际存储的长度,直接按char规定的长度分配存储空间。而varchar根据实际存储的长度分配最终的存储空间。

相同点:

1.char(n)和varchar(n)其中n都代表字符的个数。

2.超过char,varchar最大长度n的限制后,字符串会被截断。

不同点:

1.char(n)中char无论实际存储的字符数都会占用n个空间,而varchar只会占用实际字符应该占用的空间+1(实际长度length,0<=lenght<=255)或+2(length>255)。因为varchar保存字符时,除

了保存字符,还会加一个字节来保存长度(如果列声明长度大于255则用2个字节来保存)。

2.能存储的最大空间限制不一样:char的存储上限为255字节。

3.char在存储时会截断尾部的空格,但是varchar不会。

列的字符串类型可以是什么?

列的字符串类型是SET、BLOB、ENUM、CAHR、TEXT、VARCHAR。

BLOB和TEXT的区别?

BLOB是一个二进制对象,可以容纳可变数量的数据。有四种类型的BLOB:TINYBLOB,BLOB,MEDIUMBLOB,LONGBLOB。Text是一个不区分大小写的BLOB,四种类型:TINYTEXT,TEXT,MEDIUMTEXT,LONGTEXT。

BLOB保存二进制数据,TEXT保存字符数据。

四、索引

说说你对 MySQL 索引的理解?

数据库索引的原理,为什么要用 B+树,为什么不用二叉树?

聚集索引与非聚集索引的区别?

InnoDB引擎中的索引策略,了解过吗?

创建索引的方式有哪些?

聚簇索引/非聚簇索引,mysql索引底层实现,为什么不用B-tree,为什么不用hash,叶子结点存放的是数据还是指向数据的内存地址,使用索引需要注意的几个地方?

1.定义

Mysql官方对索引的定义为:索引(Index)是帮助mysql高效获取数据的排好序的数据结构,所以索引的本质是数据结构。

索引的目的在于提高查询效率,可以类比字典,图书目录等。

可以简单的理解为“排好序的快速查找数据结构”,除数据本身之外,数据库还维护着一个满足特定查找算法的数据结构,这些数据结构已某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级

查找算法。这种数据结构就是索引。

索引本身也很大,不可能全部存储在内存中,一般以索引文件对的形式存储在磁盘上。

平常说的索引如果没有特别指明,就是B+树(多路搜索树,不一定是二叉树)结构组织的索引。其中聚簇索引,次要索引,覆盖索引,复合索引,前缀索引,唯一索引默认都是B+树索引,统称索引。此外还有哈希索引。

2.基本语法

创建索引:CREATE [UNIQUE] INDEX indexName on mytable(username(length));

如果是Char或者Varchar类型,length可以小于实际长度;如果是BLOB和TEXT类型必须制定length。

修改表结构(创建索引):ALTER table tableName add [unique] INDEX indexName(columnName)。

删除索引:DROP INDEX [indexName] on myTable.

查看:SHOW INDEX FROM tableName

使用ALTER 命令:

ALTER Table tableName ADD Primary key(columen_list) 添加主键索引,索引值必须唯一且不能为空。

ALTER Table tableName ADD UNIQUE indexName(columen_list)添加唯一索引,列值必须是唯一的。

ALTER Table tableName ADD Index indexname(columen_list)添加普通索引,列值不唯一。

ALTER Table tableName ADD FullText indexName(columen_list)添加全文索引。

3.优势

提高数据检索效率,降低数据库IO成本。

降低数据排序成本,降低CPU消耗。

4.劣势

索引也是一张表,保存了主键和索引字段,并指向实体表的记录,所以也需要占用内存。

虽然提高了表的查询效率,同时会降低表的更新速度,如对表进行INSERT,UPDATE,DELETE。因为更新表时,mysql不仅要保存数据,还要保存一下索引文件每次添加更新了索引列的字段,都会调整因为键值变化

所带来的的索引变化。

5.索引分类

(1)数据结构角度:B+树索引,Hash索引,Full-Text索引,R-Tree索引。

(2)物理存储角度:聚簇索引,非聚簇索引(也叫辅助索引),聚簇索引和非聚簇索引都是B+树结构。

(3)逻辑角度:

主键索引:主键索引是一种特殊的唯一索引,不允许有空值。

普通索引(或者单列索引):每个索引只包含单个列,一个表可以有多个单列索引。

多列索引(复合索引,联合索引):符合索引是指多个列上创建索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用符合索引时遵循最左前缀集合。

唯一索引或者非唯一索引。

空间索引:空间索引是对空间数据类型的字段建立的索引,Mysql中空间数据类型有4种,分别是GEOMETRY,POINT,LINESTRING,POLYGON。Mysql中使用SPATIAL关键字进行扩展,使

得能够用于创建正规索引类型的语法创建空间索引。创建空间索引的列,必须声明为Not Null,空间索引只能在存储引擎为Myisam的表中创建。

为什么MySQL 索引中用B+tree,不用B-tree 或者其他树,为什么不用 Hash 索引?

聚簇索引/非聚簇索引,MySQL 索引底层实现,叶子结点存放的是数据还是指向数据的内存地址,使用索引需要注意的几个地方?

使用索引查询一定能提高查询的性能吗?为什么?

6.Mysql索引结构

首先要明白索引(index)是在引擎(storage engine)层面的,而不是Server层面的。

(1)B+Tree索引

Myisam和Innodb存储引擎,都是用B+Tree的数据结构,它相对于B-Tree,所有的数据都存储在叶子节点上,且把叶子节点通过指针连接在一起,形成了数据链表,以加快相邻数据的检索效率。

了解下B+Tree和B-Tree的区别:

(a)B-Tree

B-Tree是为磁盘等外存储设备设计的一种平衡查找树。

系统从磁盘读取数据到内存时是以磁盘块(block)为基本单位的,位于同一磁盘块中的文件会被一次读取出来。

Innodb存储引擎中有页(page)的概念,页是磁盘管理的最小单位。Innodb存储引擎中默认每个页的大小为16Kb,可通过参数innodb_page_size将页的大小设置为4K,8K,16K。而系统一个磁盘块的

空间没那么大,因此Innodb每次申请磁盘空间时都会是若干地址连续磁盘块来达到页的16K。Innodb再把磁盘数据读取到磁盘时会以页为基本单位,在查询数据时如果一个页中的每条数据都能有助于

定位数据记录的位置,这将会减少磁盘I/O操作,提高效率。

页的分裂与合并

(b)B+Tree

B+Tree是B-Tree的一种优化,使其更适合外存储结构,Innodb存储引擎就是B+Tree实现其索引。

B-Tree中不仅包含数据的Key值,还有data值。而每一个页的存储空间是有限的,如果data数据较大时将会导致每个节点(每个页)能存储的Key的数据量很小,当存储的数据很大时同样会导致B-Tree

的深度较大,增大磁盘的I/O数,进而影响查询效率。在B+Tree中,所有数据记录节点都是按照键值大小顺序存放在同一层的叶子节点上,而非叶子节点只存储Key值,这样可以大大提高每个节点存储

Key的数量,降低B+Tree的深度。

(c)B+Tree与B-Tree不同

B+Tree非叶子节点只存储键值信息,所有叶子节点之间有个链指针,数据记录都存放在叶子节点上。

通常在B+Tree上有两个头指针,一个指向根节点,另一个指向关键字最小的叶子节点,而且所有叶子节点(即数据节点)之间是一种链式环结构。因此B+Tree可以进行2种查找运算:一种是对于主键的

范围查找和分页查找,另一种是从根节点开始,进行随机查找。在数据库中,B+Tree的高度一般是2-4层。Mysql的Innodb存储引擎在设计时是将根节点常驻内存中的,也就是说查找某一行记录时最多

需要1~3次磁盘I/O操作。

(d)Myisam主键索引与辅助索引结构

Myisam引擎的索引文件和数据文件是分离的。Myisam引擎索引结构的叶子节点的数据域,存放的并不是实际的数据记录,而是数据记录的地址。索引文件与数据文件分离,这样的索引称为“非聚簇索引”。

Myisam的主键索引与辅助索引区别不大,只是主键索引不能有重复关键字。通过索引查找数据:先从索引文件中查找到索引节点,从中拿到数据的文件指针,再到数据文件中通过文件指针定位了具体的数据。

(e)Innodb主键索引与辅助索引结构

Innodb引擎索引结构的叶子节点的数据域,存放的是实际数据记录(对于主索引,此处会存放表中所有的数据记录;对于辅助索引此处会引用主键,检索的时候通过主键到主键索引中找到数据行)或者说Innodb

数据文件本身就是主键索引文件,这样的索引被称为“聚簇索引”,一个表只能有一个聚簇索引。

主键索引

Innodb索引是聚簇索引,它的索引和数据文件存储在.idb文件中的,因此它的索引结构是在同一个树节点中同时存放索引和数据。

辅助(非主键)索引

在底层的叶子节点上有两行数据,第一行是辅助索引,第二行值主键索引。需要回表查询。

(f)Innodb索引结构需要注意点

数据文件本身就是索引文件

表数据文件本身就是按照B+Tree组织的一个索引结构文件。

聚簇索引中叶子节点包含了完整的记录。

Innodb表必须要有主键,并且推荐使用整型自增主键

Innodb存储引擎,如果在设计表结构的时候没有显式指定主索引列的话,Mysql会从表中选择不重复的列建立索引,如果没有符合的列,则Mysql自动为Innodb生成一个隐含字段作为主键,这个字段6个字节

类型为整型。

那为什么推荐使用整型自增主键而不是选择UUID?

UUID是字符串,比整型消耗更多的存储空间;

在B+Tree中进行查找时需要跟经过的节点值比较大小,整型数据的比较运算比字符串更快速。

自增的整型索引在磁盘中会连续存储,在读取一页数据时也是连续;UUID是随机产生的,不适合范围读取。

在插入或者删除数据时,整型自增主键会在叶子节点的末尾建立新的叶子节点,不会破坏左侧子树的结构;UUID重新排序会出现磁盘的分页。

为什么非主键索引结构叶子节点存储的是主键值?

保证数据一致性和节省存储空间

(2)Hash索引

主要就是通过Hash算法(常见的Hash算法有直接定址法,平方取中法,折叠法,除数取余法,随机法),将数据库字段数据转换成定长的Hash值,与这条数据的行指针一并存入Hash表的对应位置;如果发生碰撞

则在对应的Hash键下已链表形式存储。

Mysql目前有Memory与NDB引擎支持Hash索引。

(3)Full-Text索引

全文索引也是Myisam的一种特殊索引类型,主要用于全文索引,Innodb从mysql5.6版本提供对全文索引的支持。

(4)R-Tree空间索引

空间索引是Myisam的一种特殊索引类型,主要用于地理空间数据类型。

为什么Mysql索引要用B+树不是B树?

用B+树不用B树考虑的是IO对性能的影响,B树的每个节点都存储数据,而B+树只有叶子节点才存储数据,所以查找相同数据量的情况下,B树的高度更高,IO更频繁。数据库索引是存储在磁盘上的,当数据量大时,

就不能把整个索引全部加载到内存了,只能逐一加载每个磁盘页(对应索引树的节点)。其中在Mysql底层对B+树进行进一步优化:在叶子节点中是双向链表,且在列表的头结点和尾节点也是循环指向的。

面试官:为何不采用Hash方式?

因为Hash索引底层是哈希表,哈希表是一种以key-value存储数据的结构,所以多个数据在存储关系上是完全没有任何顺序关系的,所以,对于区间查询是无法通过索引查询的,就需要全表扫面。所以Hash索引只适用

于等值查询的场景。而B+Tree是一种多路平衡查询树,所以它的节点是天然有序的(左子节点小于父节点,右子节点大于父节点),所以对于范围查询的时候不需要全表扫描。

哈希索引不支持多列联合索引的最左匹配规则,如果有大量重复键值的情况下,哈希索引的效率会很低,因为存在哈希碰撞问题。

(5)哪些情况需要建立索引

主键自动建立索引;频繁操作查询条件的字段;查询中与其他表关联的字段,外键关系建立索引;单键/组合索引的选择问题,高并发下倾向创建组合索引;查询中排序的字段,排序字段通过索引访问大幅提高

排序速度;查询中查询或者分组字段。

(6)哪些情况不要创建索引

表记录太少;经常增删改的表;数据重复且分布均匀的表字;频繁更新的字段不适合创建索引;where条件里用不到的字段不创建索引。

(7)Mysql高效索引

覆盖索引,也叫索引覆盖,也就是平时所说的不需要回表操作。

就是select的数据列只用从索引中就能取得,不必读取数据行,Mysql可以利用索引返回select列表中的字段,而不必根据索引再次读取数据文件,换句话说查询列要被所建的索引覆盖。

索引是一个高效找到行的方法,但是一般数据库也可以使用索引找到一个列的数,因此它不比读取整个行。毕竟索引叶子节点存储了它们索引的数据,当能通过只读取索引就能得到想要的数据,那就不需要读取行了。

一个索引包含(覆盖)满足查询结果的数据就叫做覆盖索引。

判断标准

使用explain,可以通过输出的extra列来判断,对于一个索引覆盖查询,显示为using index,MySql查询优化器在查询之前会决定是否有索引覆盖查询。

五、MySql查询

count(*) 和 count(1)和count(列名)区别 ps:这道题说法有点多?

执行效果上:

count(*)包括了所有的列,相当于行数,在统计结果的时候,不会忽略列值为NULL。

count(1)包括了所有列,用1代表代码行,在统计结果的时候,不会忽略列值为NULL。

count(列名)只包括列名那一列,在统计结果的时候,会忽略列值为空(null)的计数。

执行效率上:

列名为主键,count(列名)会比count(1)快

列名不为主键,count(1)会比count(列名)快

如果多个列并且没有主键,则count(1)的执行效率优于count(*)

如果有主键,则select count(主键)的执行效率最优

如果表只有一个字段,则select count(*)最优

MySQL中 in和 exists 的区别?

exists:exists对外表用loop逐条查询,每次查询都会查看exists的条件语句,当exists条件语句能返回记录行时(无论记录行是多少,只要能返回),条件就为真,返回当前loop到的这条记录;反之,如果exists

里的条件语句不能返回记录行,则当前loop到的这条记录被丢弃,exists条件就像一个bool条件,当能返回结果集则为true,不能返回结果集则为false

in:in查询相当于多个or条件的叠加。

如果查询的两个表大小相当,那么用in和exists差别不大。

如果两个表中一个较小,一个是大表,则子查询表大的用exist,则子查询表大的用exists,子查询表小的用in;

UNION和UNION ALL的区别?

UNION和UNION ALL都是将两个结果集合并为一个,两个要联合的SQL语句字段数必须一样,而且字段类型要“相容”(一致);

UNION在进行表连接后会筛选掉重复的数据记录(效率较低),而UNION ALL则不会去掉重复记录。

UNION会按照字段的顺序进行排序,而UNION ALL只是简单的将两个结果集合并就返回。

六、Mysql事务

事务的隔离级别有哪些?MySQL的默认隔离级别是什么?

什么是幻读,脏读,不可重复读呢?

MySQL事务的四大特性以及实现原理

MVCC熟悉吗,它的底层原理?

Mysql事务主要用于处理操作量大,复杂度高的数据

1、ACID事务基本要素

原子性,一致性,隔离性,持久性。

事务是由一组SQL语句组成的逻辑处理单元,具有4个属性,简称ACID属性。

A(Atomicity)原子性:整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态。

C(Consistency)一致性:在事务开始之前和事务结束之后,数据库的完整性约束没有被破坏。

I(Isolation)隔离性:一个事务的执行不能受其他事务的干扰。即一个事务内部的操作及使用的数据对其并发事务是隔离的,并发执行的各个事务之间不能互相干扰。

D(Durability)持久性:在事务完成后,该事务对数据库所做的操作便持久的存在数据库中,不会回滚。

并发事务处理带来的问题

更新丢失(Lost Update):事务A和事务B选择同一行,然后基于最初选定的值更新改行时,由于并发事务,就会发生丢失更新问题。

脏读(Dirty Reads):事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据就是脏数据。

不可重复读(Non-Repeatable Reads):事务A多次读取同一数据,事务B在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致。

幻读(Phantom Reads)幻读与不可重复读类似。它发生在一个事务A读取了几行数据,接着另一个并发事务B插入了一些数据时。在随后的查询中,事务A就会发现多了一些原本不存在的数据,就好像发生幻觉一样,所以称为幻读。

幻读和不可重复读的区别

不可重复读的重点是修改:在同一事务中,同样的条件,第一次读的数据和第二次读的数据不一样。(因为中间有其他事务提交了修改)

幻读的重点在于新增或删除:在同一事务中,同样的条件,第一次和第二次读出来的数据行不一样(因为中间有其他事务提交了插入/删除)。

并发事务处理带来的问题的解决方法

“更新丢失”通常是应该完全避免的。但为防止更新丢失,并不能单靠数据库并发控制器来解决,需要应用程序对要更新的数据加必要的锁来解决,因此防止更新丢失应该是应用程序的责任。

“脏读”,“不可重复读”,“幻读”,其实都是数据库读一致性问题,必须有数据库提供一定的事务隔离机制来解决;

一种是加锁:在读取数据前,对其加锁,防止其他事务对数据修改。

一种是数据多版本并发控制(简称MVCC或者MCC),也称为多版本数据库;不用任何锁,通过一定机制生成一个数据请求时间点的一致性数据快照,并用这个快照提供一定级别(语句级或事务级)的一致性读取。

2、事务隔离级别

数据库事务隔离级别目前有4中,有低到高分别为

读未提交(READ-UNCOMMENTED):最低的隔离级别,允许读取尚未提交的数据变更,可能会出现脏读,幻读,不可重复读。

一个事务可以读取另一个未提交事务的数据。

读已提交(READ-COMMENTED):允许读取并发事务已提交的数据,可以阻止脏读,可能会发生幻读,不可重复读。

一个事务要等另一个事务提交后才能读取数据。

可重复读(REPEATABLE-READ):对同一字段的多次读取结果都是一致的,除非数据被本身事务所修改,可以阻止脏读和不可重复读,可能会发生幻读。

开始读取数据(事务开启)时,不在允许修改操作。Mysql默认的隔离级别。

可串行化(SERIALIZABLE):最高的隔离级别,完全服从ACID的隔离级别。所有事务依次逐个执行,这样事务之间就完全不可能产生干扰,可以阻止脏读,幻读,不可重复读。

会在读取的每一行数据上都加锁,所以可能导致大量的超时锁竞争问题,比较好性能,一般不使用。

需要说明,事务隔离级别和数据库访问的并发性是对立的,事务隔离级别越高并发性越差。

Mysql Innodb存储引擎的默认隔离级别是REPEALTABLE-READ(可重读)。InnoDB存储引擎在可重复读事务隔离级别下使用的是Next-Key Lock算法,因此可以避免幻读的产生,所以说InnoDb存储引擎默认的事务隔离级别

是可重复读已经完全保证了事务的隔离性要求,即达到了SQL标准的可串行化隔离级别,而且保留了比较好的并发性能。

因为隔离级别越低,事务请求的锁越少,所以大部分数据库的隔离级别都是READ-COMMENTED读已提交;但是Innodb存储引擎默认用的可重复读,并不会有任何性能损失。

3、MVVC多版本并发控制

Mysql的大多数事务型存储引擎都不是简单的行级锁。基于提升并发性能考虑,一般都实现了多版本并发控制,包括Oracle.

可以认为MVCC是行级锁的一种变种,但它在很多情况下避免了加锁操作,因此开销更低。虽然实现机制各不相同,但都实现了非阻塞的读操作,写操作也只是锁定必要的行。

MVCC的实现是通过保存数据在某个时间点的快照实现的。也就是说不管需要执行多长时间,每个事务看到的数据都是一致的。

典型的MVCC实现方式,分为乐观并发控制和悲观并发控制。

InnoDb的MVCC是通过在每行记录后面保存两个隐藏的列来实现的。这两个列,一个保存了行的创建时间,一个保存了行的过期时间(删除时间)。当然存储的并不是真实时间,而是系统版本号。每开始一个新的

事务,系统版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的行的版本号比较。

REPEATABLE READ(可重复读)隔离级别下MVCC如何工作

SELECT,INSERT,UPDATE,DELETE。(此处面试不会问道,不做详细)

4、事务日志

Innodb使用日志来减少提交事务的开销。因为日志中已经记录了事务,就无须在每个事务提交时把缓冲池的脏块刷新到磁盘中。

Innodb用日志吧随机IO变成顺序IO。一但日志安全写到磁盘,事务就持久化了,即时断电,Innodb可以重放事务,并且恢复已提交的事务。

5、事务实现

事务的实现是基于数据库的存储引擎。不同的存储引擎对事务的支持不一样,Mysql中支持的是InnoDb,NDB。

事务是如何通过日志来实现的,说得越深入越好?

事务的隔离是通过锁实现的,而事务的原子性,一致性,持久性,是通过事务日志来实现的。

redo log(重做日志)实现持久性和原子性

undo log(回滚日志)实现一致性

二种日志均可以视为一种恢复操作,redo log是恢复提交事务修改的页操作,而undo log是回滚行记录到特定版本。二者记录的内容也不同,redo log 是物理日志,记录页的物理操作。

而undo log 是逻辑日志,根据每行记录进行修改。

又引出个问题:你知道MySQL 有多少种日志吗?

错误日志,查询日志(记录所有对数据库请求的日志),慢查询日志(设置一个阈值,超过阈值的记录),二进制日志(对数据库操作的所有日志),中继日志(二级制日志,用来给salve库回复),事务日志(redolog undolog)

分布式事务相关问题,可能还会问到 2PC、3PC?

6、Mysql对分布式事务的支持

分布式事务的实现有很多,可以采用Innodb提供的原生事务支持,也可以采用消息队列来实现,分布式事务的最终一致性。

Mysql中使用分布式事务,涉及一个或多个资源管理器和一个事务管理器。

分布式事务二阶段提交(2PC):参与者将操作成败通知协调者,再由协调者根据所有参与者反馈的情况决定是否提交还是中止操作。

(1)请求阶段(表决阶段)事务协调者通知每个参与者准备提交或者取消事务,然后进入表决过程,参与者要么在本地执行事务,写本地的redo和undo日志,但不提交。请求阶段,参与者将告知协调者自己的决策

同意(参与者本地执行成功)或取消(本地作业故障)。

(2)提交阶段(执行)基于第一阶段的结果进行决策:提交或取消。当且仅当所有参与者同意提交事务,协调者才通知所有参与者提交事务,否则协调者通知所有参与者取消事务。

(3)缺点同步阻塞问题(所有参与者都是事务阻塞型);单点故障问题(协调者故障);数据不一致(提交阶段局部网络异常)。

分布式事务三阶段提交(3PC)在参与者和协调者中都引入超时机制,并且把二段提交的第一部分分为两步:询问,然后在锁资源,最后在真正提交。

(1)canCommit阶段,3PC的canCommit阶段和2PC的请求阶段很像,协调者向参与者发送commit请求,参与者如果可以提交就返回yes,否则返回no。

(2)preCommit阶段,协调者根据参与者canCommit阶段的响应来决定是否进行事务的preCommit操作(所有为yes,有一个no或者超时)。

(3)doCommit阶段,协调者根据参与者preCommit阶段的响应来决定是否提交事务。

目前一致性还有很多算法Paxos(Zookeeper),Zab,Raft,,还有一致性hash算法,用于负载均衡。

七、Mysql锁机制

数据库的乐观锁和悲观锁?

MySQL 中有哪几种锁,列举一下?

MySQL中InnoDB引擎的行锁是怎么实现的?

MySQL 间隙锁有没有了解,死锁有没有了解,写一段会造成死锁的 sql 语句,死锁发生了如何解决,MySQL 有没有提供什么机制去解决死锁?

锁是计算机协调多个进程或线程并发访问某一字段的机制。

在数据库中,除传统的计算资源(CPU,RAM,IO等)的挣用外,数据也是一种很多用户共享的资源。数据库锁机制简单来说,就是数据库为了保证数据的一致性,而是各种共享资源在被并发访问边等有序所设计的一种规则。

1、锁的分类

(1)从对数据操作的类型分类:

读锁(共享锁),针对同一份数据,多个操作可以同时进行,不会互相影响。

写锁(排它锁),当前写操作没有完成前,他会阻断其他写锁和读锁。

(2)从对数据的操作粒度分类。

为了尽可能的提高数据库的并发度,每次锁定的范围越小越好,理论上每次只锁定当前操作的数据的方案会得到最大的并发度,但是管理锁是很耗资源的事情(涉及获取,检查,释放锁等动作),因此数据

库系统需要在高并发响应和系统性能两方面进行平衡。

表级锁,开销小,加锁快;不会死锁;锁粒度大,发生锁冲突的概率最高,并发度最低(Myisam和Memory存储引擎采用的表级锁);

行级锁,开销大,加锁慢;会出现死锁;锁定粒度较小,发生锁冲突的概率最低,并发度也是最高(Innodb默认为行级锁,但也支持表级锁)。

页面锁,开销和加锁时间介于表锁和行锁之间;会出现死锁;

适用:从锁的角度来说,表级锁更适合于已查询为主,只有少量按索引条件更新数据的应用。而行级锁更适合于有大量按索引条件并发更新少量不同的数据,同时又有并发查询的应用。

2、Myisam表锁

表共享读锁(Table Read Lock),不会阻塞其他用户对统一标的读请求,但会阻塞对同一表的写请求;

表独占写锁(Table Write Lock),会阻塞其他用户对同一表的读和写操作;

3、Innodb行锁

Innodb实现了两种类型的行锁。

共享锁(S),允许一个事务去读一行,阻止其他事务获得相同数据的排他锁。

排他锁(X),允许获得排他锁的事务更新数据,阻止其他事务取得相同数据及的共享锁和排他写锁。

为了允许行锁和表锁共存,实现多粒度锁机制。Innodb还两种内部使用的意向锁,这两种意向锁都是表锁。

意向共享锁(IS),事务打算给数据行加行共享锁,事务宰割一个数据行加共享锁前必须先取得该表的IS锁。

意向排他锁(IX),事务打算给数据行加排他锁,必须先取得该表的IX锁。

(1)加锁机制

乐观锁和悲观锁是两种并发控制的思想,可用于解决丢失更新问题

乐观锁(读多写少)会“乐观地”假定大概率不会发生并发更新冲突,访问,处理数据中不加锁,只在更新数据时再根据版本号或者时间戳判断是否有冲突,有则处理,无则提交。用数据版本(Version)

是比较常见的乐观锁。

悲观锁(写多读少)会“悲观地”假定大概率会发生并发更新重读,访问,处理数据前就加排他锁,在整个数据处理过程中锁定数据,事务提交或回滚后才释放锁。另外与乐观锁相对应,悲观锁是由数据库

自己实现的,要用的时候,直接调用数据库相关语句就可以。

(2)锁模式(Innodb有三种行锁算法)

记录锁,单个行上记录的锁。对索引项加锁,锁定符合条件的行。

SELECT * FROM table WHERE id = 1 FOR UPDATE;

UPDATE SET age = 50 WHERE id = 1;

间隙锁,当时用范围条件而不是相等条件检索数据,并请求共享和排他锁时,Innodb会给符合记录的已有数据记录加锁。对于键值在条件范围内但并不存在的记录,叫“间隙”。

SELECT * FROM table WHERE id BETWEN 1 AND 10 FOR UPDATE;

临建锁,记录锁与间隙锁的组合。

select for update有什么含义,会锁表还是锁行还是其他?

for update仅适用于Innodb,且必须在事务块(BEGIN/COMMIT)中才能有效。在进行事务操作时,通过“for update”语句,Mysql会对查询结果集中每行数据都添加排他锁,其他线程对该记录的更新

与删除操作都会阻塞。

InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才是用行级锁,否则,InnoDb将使用表锁。注意For update 仅适用于Innodb,且必须在(BEGIN/COMMIT)才能是生效。

明确主键,并且有此记录,row lock;明确主键,无此记录,无lock;无主键,table lock;主键不明确,table lock;

(3)死锁

死锁产生:死锁是两个或多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环。当事务试图以不同的顺序锁定资源时,就可能产生死锁。锁的行为和顺序和存储引擎相关。

检测死锁:Innodb存储引擎能检测到死锁的循环依赖并立即返回一个错误。

死锁恢复:死锁发生后,只有部分或者完全回滚一个事务,才能打破死锁,Innodb目前的处理方法是,将持有最少行级排他锁的事务进行回滚。

外部锁的死锁检测恢复:发生死锁后,InnoDb一般都能自动检测到,并使用其中一个锁事务释放锁并回退,另一个事务获得锁,继续完成事务。涉及外部锁和表锁的情况下,需要通过设置锁等待超时

参数innodb_lock_wait_timeout来解决。

死锁影响性能:

Innodb避免死锁:在事务开始时通过为预期要修改的每个行使用select....for update语句获取必要的锁;在事务中先申请排他锁;在事务中已相同的顺序使用加锁语句。改变事务隔离级别。

八、Mysql调优

日常工作中你是怎么优化SQL的?

SQL优化的一般步骤是什么,怎么看执行计划(explain),如何理解其中各个字段的含义?

如何写sql能够有效的使用到复合索引?

一条sql执行过长的时间,你如何优化,从哪些方面入手?

什么是最左前缀原则?什么是最左匹配原则?

1、影响Mysql性能的因素

业务需求对Mysql的影响;存储定位对Mysql的影响;Schema设计对数据库的影响;硬件环境对Mysql的影响。

2、性能分析

(1)Mysql QueryOptimizer

(22Mysql 常见瓶颈:CPU(CPU在饱和的时候一般发生在数据装入内存或从磁盘读取数据的时候)。IO(磁盘IO瓶颈发生在装入数据远大于内存容量的时候)。服务器的性能瓶颈(top,free查看系统性能状态)。

(3)性能下降SQL慢,执行时间长,等待时间长

查询语句写的烂,索引失效(单值、复合),关联查询太多join(设计缺陷或不得已的需求),服务器调优及各参数设置(缓冲,线程数等)。

(4)Mysql常见性能分析手段

在优化Mysql时,通常需要对数据库进行分析,常见的分析手段有慢查询日志,EXPLAIN分析查询,Pfofiling分析以及show命令查询系统状态,通过定位分析性能瓶颈,才能更好的优化数据库系统性能。

(5)Explain执行计划

使用Explain关键字可以模拟优化器执行SQL查询语句,从而知道sql是如何处理的。

各字段解释:

id:(select 查询的序列号,包含一组数字,表示查询中执行Select自居或操作表的顺序)

id相同,执行顺序从上而下;id全不同,如果是子查询,id的序号会递增,id值越大优先级越高越先被执行;id部分相同,执行顺序是按照数字大的先执行,然后数字相同的从上而下。

select_type:(查询类型,用于区分普通查询,联合查询,子查询等复杂查询)。

SIMPLE,简单的select查询,查询中不包含子查询或者UNION。

PRIMARY,查询中若包含复杂的子部分,最外层查询被标记为PRIMARY。

SUBQUERY,在select或者where列表中包含了子查询。

DERIVED,在from列表中包含的子查询被标记额为DERIVED,MYsql会递归执行这些子查询,把结果放在临时表。

UNION,若第二个select出现在UNION之后,则被标记为UNION,若UNION包含在from子句的子查询中,外层查询则被标记为DERIVED。

UNION RESULT,从UNION表获取结果的select。

table:(哪张表)

type:(显示查询使用了哪种类型,从最好到最差依次排列system>const>eq_ref>ref>fulltext>ref_or_null>index_merge>unique_subquery>index_subquery>range>index>all)

system,表只有一行记录(等于系统表),是const类型的特例,平时不会出现。

const,表示通过索引一次就能找到,const用于比较primarykey和unique索引,因为只要匹配一行数据所以很快,如将主键置于where查询中,mysql就能将该查询置为一个常量。

eq_ref,唯一索引扫面,对于每个索引键,表中只有一条记录与之匹配,常见于主键索引或唯一索引扫描。

ref,非唯一索引扫描,范围匹配某个单独值得所有行。本质上也是一种索引访问,他返回所有匹配某个单独值的所有行,然而,他也可能找到多个符合条件的行。

range,只检索给定范围的行,使用一个索引来选择行。key列表显示使用了哪个索引,一般就是where语句中出现了between,<,>,in等的查询,这种范围扫描要比全表扫描好。开始某个点,结束某个点,不扫描全部。

index,full index sacn,index于all区别为index类型只遍历索引树。通常比all快,因为索引文件通常比数据文件小。(也就是说all和index虽然都是读全表,但是index是从索引中读取,而all是磁盘读)。

all,将遍历全表找到匹配行。

possible_keys:(显示可能应用在这张表中的索引,一个或多个,查询涉及到的字段若存在索引,则该索引被列出)。

key:(实际使用的索引,如果NULL,则没有使用索引。查询若使用了覆盖索引,则该索引和查询的select字段重叠,仅出现在key列表中)

key_len:(表示索引中使用的字节数,可通过该列计算查询中使用到的索引的长度,在不损失精度的情况下,长度越短越好。key_len显示的值为索引字段的最大长度,并非实际长度)。

ref:(显示索引的哪一列被使用了,如果可能的话,是一个常数。那些列或者常量被用于查找列上索引的值)。

rows:(根据表统计信息及索引选用情况,大致估算找到所需要的记录所要的行数)。

extra:(包括不适合在其他列上显示的重要信息)。

(1)using filesort:说明mysql会对数据使用一个外部的索引排序。不是按照表内的数据进行读取。mysql中无法通过索引完成的排序称为“文件排序”。常见于orderby 和gourpby中。

(2)using temporary:使用了临时表保存中间结果,mysql在对查询结果进行排序时使用临时表。

(3)using index:表示相应的select操作中使用了覆盖索引,避免了访问表的数据行,效率不错,如果同时出现using where,表明索引被用来进行索引键值的查找;否则索引被用来读取数据而非执行查找操作。

(4)using where:使用了where 过滤。

(5)using where using index:查询的列被索引覆盖,并且where筛选条件是索引列之一但是不是索引的前导列。

(6)NULL:查询的列未被索引覆盖并且where筛选条件是索引的前导列,意味着用到了索引,但是部分字段未被索引覆盖,必须通过“回表”来实现,不是纯粹的用到索引,也不完全是没有用到索引。

(5)using join buffer:使用了链接缓存。

(6)impossible where:where 子句的查询总是false,不能用来获取任何数据。

(7)select tables optimzed aways:在没有group子句的情况下

(8)distinct: 优化distinct操作。

慢查询日志,long_query_time的默认值是10,意思是10秒上的语句。默认情况下,Mysql数据库没有开启慢查询日志,需要手动设置参数开启。

3、性能优化

(1)索引优化

全值匹配

最佳左前缀法则,比如建立一个索引(a,b,c)那么我们可以利用的索引就有(a),(ab),(abc)

不在索引列上做任何操作(计算,函数,(自动or手动)类型转换 ),会导致索引失效而转向全面扫表。

存储引擎不能使用索引中范围条件右边的列。

尽量使用覆盖索引(只访问索引的查询(索引列和查询列一直)),减少select。

is null,is not null 也无法使用索引。

like“xxxx%”是可以使用到索引,like"%xxx"则不行同理(like"%XXX%")

字符串不加单引号索引失效

少用or,用他来链接是索引会失效。

<,<=,=>...Between,in可以使用到索引,<>,not in,!=则不行,会导致全表扫描。

(2)一般性建议

对于单键索引,尽量选择针对当前query过滤性更好的索引。

在选择组合索引的时候,当前query中过滤性最好的字段在索引字段顺序中,位置越靠前越好(越靠左越好)。

在选择组合索引的时候,尽量选择可以能够包含当前query中的where子句中更多字段的索引。

尽可能通过分析统计信息和调整query的写法来达到选择合适索引的目的。

少用hint强制索引。

(3)查询优化

永远小表驱动大表(小的数据集驱动大的数据集)

select * from A where id in (select id from B)`,当B表的数据集小于A表时,用in优于exists。

select * from A where exists (select 1 from B where B.id=A.id) ,当A表数据小于B表时,用exist优于in。

(4)order by 关键字优化

order by子句,尽量使用index方式排序,避免使用file sort.

order by 满足两种情况会用index方式排序;orderby使用索引最左前列;使用where子句与orderby子句条件列满足索引最左前列。

尽可能在索引列上完成排序操作。

(5)group by关键字优化

group by实质是先排序后进行分组的,遵循索引的最佳左前缀。

where高于having,能写在where限定条件就不要卸载having中。

(6)数据类型优化

更小的通常更好:一般情况下,应该尽可能使用可以正确存储数据的最小数据类型。

简单就好:简单的数据类型通常需要更少的cpu周期。例如整数比字符操作代价更低。

尽量避免NULL:通常情况下最好指定NOT NULL。

九、分区、分库、分表

1、分区

当数据量较大时(一般千万记录级别以上),Mysql性能就会开始下降,这时需要将数据分散到多组存储文件。

逻辑数据分割;提高单一的写和读应用速度;提高分区范围读查询的速度;分割数据能够有多个不同的物理文件路径;高效的保存历史数据。

分区类型操作:RANGE分区,LIST分区,HASH分区,KEY分区。

分区表,分区键设计不太灵活,如果不走分区键,很容易出现全表扫描;一旦数据并发上来,分区表实施关联,就是灾难。

2、分表(垂直拆分,水平拆分)

垂直拆分:垂直分表,通常是按照业务功能的使用频次,把主要的热门的字段放在一起作为主要表。然后不常用,按照各自业务线聚集,拆分到不同的次要表中。主要表和次要表一对一。

水平拆分:单表的容量不超过500W,否则建议水平拆分。同一个结构的不同表,数据按照一定的规则划分,分别存储到这些表中,从而保证单表的容量不会太大,提升性能;

水平分割的方法:使用MD5哈希。取余计算,热度拆分。

3、分库

数据库集群都是master-slave;基本满足了读取操作;但是写入或者说大数据,频繁的写入操作对master性能影响比较大,这个时候单库并不能解决大规模并发问题。所以考虑分库。

一个库里面表太多,导致海量数据,系统性能下降,把原本存储于一个库的表拆分存储到多个库山。

优点:减少增量数据写入时的锁对查询影响。由于单表数量下降,常见的查询操作由于减少了需要扫面的记录,使得单表单次传所需检索行数变少,减少磁盘IO.

分库分表后难题:分布式问题,数据的完整性和一致性问题。跨库联合查询问题。事务问题。

分库分表工具:sharding-sphere:jar,前身是sharding-jdbc;TDDL:jar,Taobao Distribute Data Layer;Mycat:中间件。

十、主从复制

1、基本原理

slave会从master读取binlog进行数据同步

2、基本原则

每个slave只有一个master;每个slave只能有一个唯一的服务器Id;每个master可以有多个slave.

3、最大问题

延时

十一、其他问题

三大范式

第一范式:(列都是不可再拆分的)

数据库表中的字段都是单一属性的,不可在拆分。这个是单一属性由基本类型构成

第二范式:(每个表只描述一件事情)

数据库表中不存在非关键字段对任一候选字段的部分函数依赖(部分函数依赖指的是存在组合关键字中的某些字段决定非关键字段的情况),也即所有非关键字段都完全依赖于任一组候选字段。

首先满足第一范式,并且表中非主键列不存在对主键的部分依赖。

第三范式:(不存在对非主键列的传递依赖)

满足第二范式,并且表中的列不存在对非主键列的传递依赖。

十二、索引下推

索引下推:简称ICP,在Mysql5.6版本之后推出。

在不使用ICP的情况下,在使用非主键索引(普通索引或者二级索引)进行查询时,存储引擎通过索引检索到数据,然后返回给服务器,服务器然后判断数据是否负符合条件。

使用ICP的情况下,如果存在某些被索引的列判断条件时,Mysql将这一部分判断条件传递给存储引擎,然后由存储殷勤判断索引是否符合mysql服务器传递的条件 ,只有当索引符合条件时才会将数据检索出来返回。

索引下推,可以减少存储引擎查询基础表的此处,也可以减少Mysql服务器从存储引擎接收数据的次数。

所谓的前导列是在创建复合索引语句的第一列或者连续的多列。

using index:查询的列被索引覆盖,并且where筛选条件是索引的前导列,是性能高的表现。

mysql> explain select film_id from film_actor where film_id = 1;

using where:查询的列未被索引覆盖,where筛选条件为索引的前导列。

mysql> explain select * from actor where name = 'a';

using where using index:查询的列被索引覆盖,并且where筛选条件是索引列之一但是不是索引的前导列,意味着无法直接通过索引查找来查询符合条件的数据。

mysql> explain select film_id from film_actor where actor_id = 1;

NULL:查询的列未被索引覆盖,并且where筛选条件试试索引的前导列,意味着用到了索引,但是部分字段未被索引覆盖,必须通过“回表”来实现,不是纯粹的用到索引,也不完全是没有用到索引。

mysql>explain select * from film_actor where film_id = 1;

using index condition:与using where 类似,查询的列不完全被索引覆盖,where条件中是一个前导列的范围。

mysql> explain select * from film_actor where film_id > 1;

using tempory:需要创建零时表来处理查询。出现这种情况一般需要进行优化,首先是想用索引来优化。

actor.name没有索引,此时创建了张临时表来distinct

mysql> explain select distinct name from actor;

film.name建立了idx_name索引,此时查询时extra是using index,没有用临时表

mysql> explain select distinct name from film;

using filesort:对查询结果使用一个外部索引排序,而不是按照索引一次从表里面读取。这种情况也是需要优化

actor.name未创建索引,会浏览actor整个表,保存排序关键字name和对应的id,然后排序name并检索行记录

mysql> explain select * from actor order by name;

film.name建立了idx_name索引,此时查询时extra是using index

mysql> explain select * from film order by name;

十三、实践

explain

select * from table where col_1=1 and col_2=2

and createtime>='2020-06-01' and createtime<='2020-06-11' order by id limit 0,10

explain

select * from table where col_1=1 and col_2=2

and createtime>='2020-06-01' and createtime<='2020-06-11' order by createtime limit 0,10