学习完这篇Mysql,我在面试道路上遇神杀神遇佛杀佛


gif5新文件(1).gif

数据库基础知识html

为何要使用数据库java

数据保存在内存mysql

优势: 存取速度快算法

缺点: 数据不能永久保存sql

数据保存在文件数据库

优势: 数据永久保存缓存

缺点:1)速度比内存操做慢,频繁的IO操做。2)查询数据不方便安全

数据保存在数据库性能优化

1)数据永久保存服务器

2)使用SQL语句,查询方便效率高。

3)管理数据方便

什么是SQL?

结构化查询语言(Structured Query Language)简称SQL,是一种数据库查询语言。

做用:用于存取数据、查询、更新和管理关系数据库系统。

什么是MySQL?

MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Management System,关系数据库管理系统) 应用软件之一。在Java企业级开发中很是经常使用,由于 MySQL 是开源免费的,而且方便扩展。

数据库三大范式是什么

第一范式:每一个列都不能够再拆分。

第二范式:在第一范式的基础上,非主键列彻底依赖于主键,而不能是依赖于主键的一部分。

第三范式:在第二范式的基础上,非主键列只依赖于主键,不依赖于其余非主键。

在设计数据库结构的时候,要尽可能遵照三范式,若是不遵照,必须有足够的理由。好比性能。事实上咱们常常会为了性能而妥协数据库的设计。

mysql有关权限的表都有哪几个

MySQL服务器经过权限表来控制用户对数据库的访问,权限表存放在mysql数据库里,由mysql_install_db脚本初始化。这些权限表分别user,db,table_priv,columns_priv和host。下面分别介绍一下这些表的结构和内容:

user权限表:记录容许链接到服务器的用户账号信息,里面的权限是全局级的。

db权限表:记录各个账号在各个数据库上的操做权限。

table_priv权限表:记录数据表级的操做权限。

columns_priv权限表:记录数据列级的操做权限。

host权限表:配合db权限表对给定主机上数据库级操做权限做更细致的控制。这个权限表不受GRANT和REVOKE语句的影响。

MySQL的binlog有有几种录入格式?分别有什么区别?

有三种格式,statementrowmixed

statement模式下,每一条会修改数据的sql都会记录在binlog中。不须要记录每一行的变化,减小了binlog日志量,节约了IO,提升性能。因为sql的执行是有上下文的,所以在保存的时候须要保存相关的信息,同时还有一些使用了函数之类的语句没法被记录复制。

row级别下,不记录sql语句上下文相关信息,仅保存哪条记录被修改。记录单元为每一行的改动,基本是能够所有记下来可是因为不少操做,会致使大量行的改动(好比alter table),所以这种模式的文件保存的信息太多,日志量太大。

mixed,一种折中的方案,普通操做使用statement记录,当没法使用statement的时候使用row。

此外,新版的MySQL中对row级别也作了一些优化,当表结构发生变化的时候,会记录语句而不是逐行记录。

数据类型

mysql有哪些数据类型

一、整数类型,包括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存储的内容超出设置的长度时,内容一样会被截断。

使用策略:

对于常常变动的数据来讲,CHAR比VARCHAR更好,由于CHAR不容易产生碎片。

对于很是短的列,CHAR比VARCHAR在存储空间上更有效率。

使用时要注意只分配须要的空间,更长的列排序时会消耗更多内存。

尽可能避免使用TEXT/BLOB类型,查询时会使用临时表,致使严重的性能开销。

四、枚举类型(ENUM),把不重复的数据存储为一个预约义的集合。

有时能够使用ENUM代替经常使用的字符串类型。

ENUM存储很是紧凑,会把列表值压缩到一个或两个字节。

ENUM在内部存储时,其实存的是整数。

尽可能避免使用数字做为ENUM枚举的常量,由于容易混乱。

排序是按照内部存储的整数

五、日期和时间类型,尽可能使用timestamp,空间效率高于datetime,

用整数保存时间戳一般不方便处理。

若是须要存储微妙,能够使用bigint存储。

看到这里,这道真题是否是就比较容易回答了。

引擎

MySQL存储引擎MyISAM与InnoDB区别

存储引擎Storage engine:MySQL中的数据、索引以及其余对象是如何存储的,是一套文件系统的实现。

经常使用的存储引擎有如下:

Innodb引擎:Innodb引擎提供了对数据库ACID事务的支持。而且还提供了行级锁和外键的约束。它的设计的目标就是处理大数据容量的数据库系统。

MyIASM引擎(本来Mysql的默认引擎):不提供事务的支持,也不支持行级锁和外键。

MEMORY引擎:全部的数据都在内存中,数据的处理速度快,可是安全性不高。

MyISAM与InnoDB区别

MyISAM索引与InnoDB索引的区别?

  1. InnoDB索引是聚簇索引,MyISAM索引是非聚簇索引。
  2. InnoDB的主键索引的叶子节点存储着行数据,所以主键索引很是高效。
  3. MyISAM索引的叶子节点存储的是行数据地址,须要再寻址一次才能获得数据。
  4. InnoDB非主键索引的叶子节点存储的是主键和其余带索引的列数据,所以查询时作到覆盖索引会很是高效。

InnoDB引擎的4大特性

  1. 插入缓冲(insert buffer)
  2. 二次写(double write)
  3. 自适应哈希索引(ahi)
  4. 预读(read ahead)

存储引擎选择

若是没有特别的需求,使用默认的Innodb便可。

MyISAM:以读写插入为主的应用程序,好比博客系统、新闻门户网站。

Innodb:更新(删除)操做频率也高,或者要保证数据的完整性;并发量高,支持事务和外键。好比OA自动化办公系统。

索引

什么是索引?

索引是一种特殊的文件(InnoDB数据表上的索引是表空间的一个组成部分),它们包含着对数据表里全部记录的引用指针

索引是一种数据结构。数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现一般使用B树及其变种B+树。

更通俗的说,索引就至关于目录。为了方便查找书中的内容,经过对内容创建索引造成目录。索引是一个文件,它是要占据物理空间的。

索引有哪些优缺点?

索引的优势

能够大大加快数据的检索速度,这也是建立索引的最主要的缘由。

经过使用索引,能够在查询的过程当中,使用优化隐藏器,提升系统的性能。

索引的缺点

时间方面:建立索引和维护索引要耗费时间,具体地,当对表中的数据进行增长、删除和修改的时候,索引也要动态的维护,会下降增/改/删的执行效率;

空间方面:索引须要占物理空间。

索引使用场景(重点)

where

上图中,根据id查询记录,由于id字段仅创建了主键索引,所以此SQL执行可选的索引只有主键索引,若是有多个,最终会选一个较优的做为检索的依据。

-- 增长一个没有创建索引的字段
alter table innodb1 add sex char(1);
-- 按sex检索时可选的索引为null
EXPLAIN SELECT * from innodb1 where sex='男';

能够尝试在一个字段未创建索引时,根据该字段查询的效率,而后对该字段创建索引( alter table 表名 add index(字段名)),一样的SQL执行的效率,你会发现查询效率会有明显的提高(数据量越大越明显)。

order by

当咱们使用order by将查询结果按照某个字段排序时,若是该字段没有创建索引,那么执行计划会将查询出的全部数据使用外部排序(将数据从硬盘分批读取到内存使用内部排序,最后合并排序结果),这个操做是很影响性能的,由于须要将查询涉及到的全部数据从磁盘中读到内存(若是单条数据过大或者数据量过多都会下降效率),更不管读到内存以后的排序了。

可是若是咱们对该字段创建索引alter table 表名 add index(字段名),那么因为索引自己是有序的,所以直接按照索引的顺序和映射关系逐条取出数据便可。并且若是分页的,那么只用取出索引表某个范围内的索引对应的数据,而不用像上述那取出全部数据进行排序再返回某个范围内的数据。(从磁盘取数据是最影响性能的)

join

join语句匹配关系( on)涉及的字段创建索引可以提升效率

索引覆盖

若是要查询的字段都创建过索引,那么引擎会直接在索引表中查询而不会访问原始数据(不然只要有一个字段没有创建索引就会作全表扫描),这叫索引覆盖。所以咱们须要尽量的在select后只写必要的查询字段,以增长索引覆盖的概率。

这里值得注意的是不要想着为每一个字段创建索引,由于优先使用索引的优点就在于其体积小。

索引有哪几种类型?

主键索引: 数据列不容许重复,不容许为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);建立全文索引

索引的数据结构(b树,hash)

索引的数据结构和具体存储引擎的实现有关,在MySQL中使用较多的索引有Hash索引,B+树索引等,而咱们常用的InnoDB存储引擎的默认索引实现为:B+树索引。对于哈希索引来讲,底层的数据结构就是哈希表,所以在绝大多数需求为单条记录查询的时候,能够选择哈希索引,查询性能最快;其他大部分场景,建议选择BTree索引。

1)B树索引

mysql经过存储引擎取数据,基本上90%的人用的就是InnoDB了,按照实现方式分,InnoDB的索引类型目前只有两种:BTREE(B树)索引和HASH索引。B树索引是Mysql数据库中使用最频繁的索引类型,基本全部存储引擎都支持BTree索引。一般咱们说的索引不出意外指的就是(B树)索引(实际是用B+树实现的,由于在查看表索引时,mysql一概打印BTREE,因此简称为B树索引)

查询方式:

主键索引区:PI(关联保存的时数据的地址)按主键查询,

普通索引区:si(关联的id的地址,而后再到达上面的地址)。因此按主键查询,速度最

B+tree性质:

  • n棵子tree的节点包含n个关键字,不用来保存数据而是保存数据的索引。
  • 全部的叶子结点中包含了所有关键字的信息,及指向含这些关键字记录的指针,且叶子结点自己依关键字的大小自小而大顺序连接。
  • 全部的非终端结点能够当作是索引部分,结点中仅含其子树中的最大(或最小)关键字。
  • B+ 树中,数据对象的插入和删除仅在叶节点上进行。
  • B+树有2个头指针,一个是树的根节点,一个是最小关键码的叶节点。

2)哈希索引

简要说下,相似于数据结构中简单实现的HASH表(散列表)同样,当咱们在mysql中用哈希索引时,主要就是经过Hash算法(常见的Hash算法有直接定址法、平方取中法、折叠法、除数取余法、随机数法),将数据库字段数据转换成定长的Hash值,与这条数据的行指针一并存入Hash表的对应位置;若是发生Hash碰撞(两个不一样关键字的Hash值相同),则在对应Hash键下以链表形式存储。固然这只是简略模拟图。

索引的基本原理

索引用来快速地寻找那些具备特定值的记录。若是没有索引,通常来讲执行查询时遍历整表。

索引的原理很简单,就是把无序的数据变成有序的查询

  1. 把建立了索引的列的内容进行排序
  2. 对排序结果生成倒排表
  3. 在倒排表内容上拼上数据地址链
  4. 在查询的时候,先拿到倒排表内容,再取出数据地址链,从而拿到具体数据

索引算法有哪些?

索引算法有 BTree算法和Hash算法

BTree算法

BTree是最经常使用的mysql数据库索引算法,也是mysql默认的算法。由于它不只能够被用在=,>,>=,<,<=和between这些比较操做符上,并且还能够用于like操做符,只要它的查询条件是一个不以通配符开头的常量, 例如:

-- 只要它的查询条件是一个不以通配符开头的常量
select * from user where name like 'jack%'; 
-- 若是一通配符开头,或者没有使用常量,则不会使用索引,例如: 
select * from user where name like '%jack';

Hash算法

Hash Hash索引只能用于对等比较,例如=,<=>(至关于=)操做符。因为是一次定位数据,不像BTree索引须要从根节点到枝节点,最后才能访问到页节点这样屡次IO访问,因此检索效率远高于BTree索引。

索引设计的原则?

  1. 适合索引的列是出如今where子句中的列,或者链接子句中指定的列
  2. 基数较小的类,索引效果较差,没有必要在此列创建索引
  3. 使用短索引,若是对长字符串列进行索引,应该指定一个前缀长度,这样可以节省大量索引空间
  4. 不要过分索引。索引须要额外的磁盘空间,并下降写操做的性能。在修改表内容的时候,索引会进行更新甚至重构,索引列越多,这个时间就会越长。因此只保持须要的索引有利于查询便可。

建立索引的原则(重中之重)

索引虽好,但也不是无限制的使用,最好符合一下几个原则

1) 最左前缀匹配原则,组合索引很是重要的原则,mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就中止匹配,好比a = 1 and b = 2 and c > 3 and d = 4 若是创建(a,b,c,d)顺序的索引,d是用不到索引的,若是创建(a,b,d,c)的索引则均可以用到,a,b,d的顺序能够任意调整。

2)较频繁做为查询条件的字段才去建立索引

3)更新频繁字段不适合建立索引

4)如果不能有效区分数据的列不适合作索引列(如性别,男女未知,最多也就三种,区分度实在过低)

5)尽可能的扩展索引,不要新建索引。好比表中已经有a的索引,如今要加(a,b)的索引,那么只须要修改原来的索引便可。

6)定义有外键的数据列必定要创建索引。

7)对于那些查询中不多涉及的列,重复值比较多的列不要创建索引。

8)对于定义为text、image和bit的数据类型的列不要创建索引。

建立索引的三种方式,删除索引

第一种方式:在执行CREATE TABLE时建立索引

CREATE TABLE user_index2 (
    id INT auto_increment PRIMARY KEY,
    first_name VARCHAR (16),
    last_name VARCHAR (16),
    id_card VARCHAR (18),
    information text,
    KEY name (first_name, last_name),
    FULLTEXT KEY (information),
    UNIQUE KEY (id_card)
);

第二种方式:使用ALTER TABLE命令去增长索引

ALTER TABLE table_name ADD INDEX index_name (column_list);

ALTER TABLE用来建立普通索引、UNIQUE索引或PRIMARY KEY索引。

其中table_name是要增长索引的表名,column_list指出对哪些列进行索引,多列时各列之间用逗号分隔。

索引名index_name可本身命名,缺省时,MySQL将根据第一个索引列赋一个名称。另外,ALTER TABLE容许在单个语句中更改多个表,所以能够在同时建立多个索引

第三种方式:使用CREATE INDEX命令建立

CREATE INDEX index_name ON table_name (column_list);

CREATE INDEX可对表增长普通索引或UNIQUE索引。(可是,不能建立PRIMARY KEY索引)

删除索引

根据索引名删除普通索引、惟一索引、全文索引:alter table 表名 drop KEY 索引名

alter table user_index drop KEY name;
alter table user_index drop KEY id_card;
alter table user_index drop KEY information;

删除主键索引:alter table 表名 drop primary key(由于主键只有一个)。这里值得注意的是,若是主键自增加,那么不能直接执行此操做(自增加依赖于主键索引)

须要取消自增加再行删除:

alter table user_index
-- 从新定义字段
MODIFY id int,
drop PRIMARY KEY

但一般不会删除主键,由于设计主键必定与业务逻辑无关。

建立索引时须要注意什么?

非空字段:应该指定列为NOT NULL,除非你想存储NULL。在mysql中,含有空值的列很难进行查询优化,由于它们使得索引、索引的统计信息以及比较运算更加复杂。你应该用0、一个特殊的值或者一个空串代替空值;

取值离散大的字段:(变量各个取值之间的差别程度)的列放到联合索引的前面,能够经过count()函数查看字段的差别值,返回值越大说明字段的惟一值越多字段的离散程度高;

索引字段越小越好:数据库的数据存储以页为单位一页存储的数据越多一次IO操做获取的数据越大效率越高。

使用索引查询必定能提升查询的性能吗?为何

一般,经过索引查询数据比全表扫描要快。可是咱们也必须注意到它的代价。

  1. 索引须要空间来存储,也须要按期维护, 每当有记录在表中增减或索引列被修改时,索引自己也会被修改。 这意味着每条记录的INSERT,DELETE,UPDATE将为此多付出4,5 次的磁盘I/O。 由于索引须要额外的存储空间和处理,那些没必要要的索引反而会使查询反应时间变慢。使用索引查询不必定能提升查询性能,索引范围查询(INDEX RANGE SCAN)适用于两种状况:
  2. 基于一个范围的检索,通常查询返回结果集小于表中记录数的30%
  3. 基于非惟一性索引的检索

百万级别或以上的数据如何删除

关于索引:因为索引须要额外的维护成本,由于索引文件是单独存在的文件,因此当咱们对数据的增长,修改,删除,都会产生额外的对索引文件的操做,这些操做须要消耗额外的IO,会下降增/改/删的执行效率。因此,在咱们删除数据库百万级别数据的时候,查询MySQL官方手册得知删除数据的速度和建立的索引数量是成正比的。

  1. 因此咱们想要删除百万数据的时候能够先删除索引(此时大概耗时三分多钟)
  2. 而后删除其中无用数据(此过程须要不到两分钟)
  3. 删除完成后从新建立索引(此时数据较少了)建立索引也很是快,约十分钟左右。
  4. 与以前的直接删除绝对是要快速不少,更别说万一删除中断,一切删除会回滚。那更是坑了。

前缀索引

语法:index(field(10)),使用字段值的前10个字符创建索引,默认是使用字段的所有内容创建索引。

前提:前缀的标识度高。好比密码就适合创建前缀索引,由于密码几乎各不相同。

实操的难度:在于前缀截取的长度。

咱们能够利用select count(*)/count(distinct left(password,prefixLen));,经过从调整prefixLen的值(从1自增)查看不一样前缀长度的一个平均匹配度,接近1时就能够了(表示一个密码的前prefixLen个字符几乎能肯定惟一一条记录)

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

  1. 顾名思义,就是最左优先,在建立多列索引时,要根据业务需求,where子句中使用最频繁的一列放在最左边。
  2. 最左前缀匹配原则,很是重要的原则,mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就中止匹配,好比a = 1 and b = 2 and c > 3 and d = 4 若是创建(a,b,c,d)顺序的索引,d是用不到索引的,若是创建(a,b,d,c)的索引则均可以用到,a,b,d的顺序能够任意调整。
  3. =和in能够乱序,好比a = 1 and b = 2 and c = 3 创建(a,b,c)索引能够任意顺序,mysql的查询优化器会帮你优化成索引能够识别的形式

B树和B+树的区别

  1. 在B树中,你能够将键和值存放在内部节点和叶子节点;但在B+树中,内部节点都是键,没有值,叶子节点同时存放键和值。
  2. B+树的叶子节点有一条链相连,而B树的叶子节点各自独立。

使用B树的好处

B树能够在内部节点同时存储键和值,所以,把频繁访问的数据放在靠近根节点的地方将会大大提升热点数据的查询效率。这种特性使得B树在特定数据重复屡次查询的场景中更加高效。

使用B+树的好处

因为B+树的内部节点只存放键,不存放值,所以,一次读取,能够在内存页中获取更多的键,有利于更快地缩小查找范围。 B+树的叶节点由一条链相连,所以,当须要进行一次全数据遍历的时候,B+树只须要使用O(logN)时间找到最小的一个节点,而后经过链进行O(N)的顺序遍历便可。而B树则须要对树的每一层进行遍历,这会须要更多的内存置换次数,所以也就须要花费更多的时间

Hash索引和B+树全部有什么区别或者说优劣呢?

首先要知道Hash索引和B+树索引的底层实现原理:

hash索引底层就是hash表,进行查找时,调用一次hash函数就能够获取到相应的键值,以后进行回表查询得到实际数据。B+树底层实现是多路平衡查找树。对于每一次的查询都是从根节点出发,查找到叶子节点方能够得到所查键值,而后根据查询判断是否须要回表查询数据。

那么能够看出他们有如下的不一样:

  1. hash索引进行等值查询更快(通常状况下),可是却没法进行范围查询。
  2. 由于在hash索引中通过hash函数创建索引以后,索引的顺序与原顺序没法保持一致,不能支持范围查询。而B+树的的全部节点皆遵循(左节点小于父节点,右节点大于父节点,多叉树也相似),自然支持范围。
  3. hash索引不支持使用索引进行排序,原理同上。
  4. hash索引不支持模糊查询以及多列索引的最左前缀匹配。原理也是由于hash函数的不可预测。AAAA和AAAAB的索引没有相关性。
  5. hash索引任什么时候候都避免不了回表查询数据,而B+树在符合某些条件(聚簇索引,覆盖索引等)的时候能够只经过索引完成查询。
  6. hash索引虽然在等值查询上较快,可是不稳定。性能不可预测,当某个键值存在大量重复的时候,发生hash碰撞,此时效率可能极差。而B+树的查询效率比较稳定,对于全部的查询都是从根节点到叶子节点,且树的高度较低。

所以,在大多数状况下,直接选择B+树索引能够得到稳定且较好的查询速度。而不须要使用hash索引。

数据库为何使用B+树而不是B树

B树只适合随机检索,而B+树同时支持随机检索和顺序检索;

  1. B+树空间利用率更高,可减小I/O次数,磁盘读写代价更低。通常来讲,索引自己也很大,不可能所有存储在内存中,所以索引每每以索引文件的形式存储的磁盘上。这样的话,索引查找过程当中就要产生磁盘I/O消耗。B+树的内部结点并无指向关键字具体信息的指针,只是做为索引使用,其内部结点比B树小,盘块能容纳的结点中关键字数量更多,一次性读入内存中能够查找的关键字也就越多,相对的,IO读写次数也就下降了。而IO读写次数是影响索引检索效率的最大因素;
  2. B+树的查询效率更加稳定。B树搜索有可能会在非叶子结点结束,越靠近根节点的记录查找时间越短,只要找到关键字便可肯定记录的存在,其性能等价于在关键字全集内作一次二分查找。而在B+树中,顺序检索比较明显,随机检索时,任何关键字的查找都必须走一条从根节点到叶节点的路,全部关键字的查找路径长度相同,致使每个关键字的查询效率至关。
  3. B-树在提升了磁盘IO性能的同时并无解决元素遍历的效率低下的问题。B+树的叶子节点使用指针顺序链接在一块儿,只要遍历叶子节点就能够实现整棵树的遍历。并且在数据库中基于范围的查询是很是频繁的,而B树不支持这样的操做。
  4. 增删文件(节点)时,效率更高。由于B+树的叶子节点包含全部关键字,并以有序的链表结构存储,这样可很好提升增删效率。

B+树在知足聚簇索引和覆盖索引的时候不须要回表查询数据

在B+树的索引中,叶子节点可能存储了当前的key值,也可能存储了当前的key值以及整行的数据,这就是聚簇索引和非聚簇索引。 在InnoDB中,只有主键索引是聚簇索引,若是没有主键,则挑选一个惟一键创建聚簇索引。若是没有惟一键,则隐式的生成一个键来创建聚簇索引。

当查询使用聚簇索引时,在对应的叶子节点,能够获取到整行数据,所以不用再次进行回表查询。

什么是聚簇索引?什么时候使用聚簇索引与非聚簇索引

聚簇索引:将数据存储与索引放到了一块,找到索引也就找到了数据

非聚簇索引:将数据存储于索引分开结构,索引结构的叶子节点指向了数据的对应行,myisam经过key_buffer把索引先缓存到内存中,当须要访问数据时(经过索引访问数据),在内存中直接搜索索引,而后经过索引找到磁盘相应数据,这也就是为何索引不在key buffer命中时,速度慢的缘由

澄清一个概念:innodb中,在聚簇索引之上建立的索引称之为辅助索引,辅助索引访问数据老是须要二次查找,非聚簇索引都是辅助索引,像复合索引、前缀索引、惟一索引,辅助索引叶子节点存储的再也不是行的物理位置,而是主键值

什么时候使用聚簇索引与非聚簇索引

非聚簇索引必定会回表查询吗?

不必定,这涉及到查询语句所要求的字段是否所有命中了索引,若是所有命中了索引,那么就没必要再进行回表查询。

举个简单的例子,假设咱们在员工表的年龄上创建了索引,那么当进行select age from employee where age < 20的查询时,在索引的叶子节点上,已经包含了age信息,不会再次进行回表查询。

联合索引是什么?为何须要注意联合索引中的顺序?

MySQL能够使用多个字段同时创建一个索引,叫作联合索引。在联合索引中,若是想要命中索引,须要按照创建索引时的字段顺序挨个使用,不然没法命中索引。

具体缘由为:

MySQL使用索引时须要索引有序,假设如今创建了"name,age,school"的联合索引,那么索引的排序为: 先按照name排序,若是name相同,则按照age排序,若是age的值也相等,则按照school进行排序。

当进行查询时,此时索引仅仅按照name严格有序,所以必须首先使用name字段进行等值查询,以后对于匹配到的列而言,其按照age字段严格有序,此时能够使用age字段用作索引查找,以此类推。所以在创建联合索引的时候应该注意索引列的顺序,通常状况下,将查询需求频繁或者字段选择性高的列放在前面。此外能够根据特例的查询或者表结构进行单独的调整。

事务

什么是数据库事务?

事务是一个不可分割的数据库操做序列,也是数据库并发控制的基本单位,其执行的结果必须使数据库从一种一致性状态变到另外一种一致性状态。事务是逻辑上的一组操做,要么都执行,要么都不执行。

事务最经典也常常被拿出来讲例子就是转帐了。

假如小明要给小红转帐1000元,这个转帐会涉及到两个关键操做就是:将小明的余额减小1000元,将小红的余额增长1000元。万一在这两个操做之间忽然出现错误好比银行系统崩溃,致使小明余额减小而小红的余额没有增长,这样就不对了。事务就是保证这两个关键操做要么都成功,要么都要失败。

事物的四大特性(ACID)介绍一下?

关系性数据库须要遵循ACID规则,具体内容以下:

  1. 原子性: 事务是最小的执行单位,不容许分割。事务的原子性确保动做要么所有完成,要么彻底不起做用;
  2. 一致性: 执行事务先后,数据保持一致,多个事务对同一个数据读取的结果是相同的;
  3. 隔离性: 并发访问数据库时,一个用户的事务不被其余事务所干扰,各并发事务之间数据库是独立的;
  4. 持久性: 一个事务被提交以后。它对数据库中数据的改变是持久的,即便数据库发生故障也不该该对其有任何影响。

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

  • 脏读(Drity Read):某个事务已更新一份数据,另外一个事务在此时读取了同一份数据,因为某些缘由,前一个RollBack了操做,则后一个事务所读取的数据就会是不正确的。
  • 不可重复读(Non-repeatable read):在一个事务的两次查询之中数据不一致,这多是两次查询过程当中间插入了一个事务更新的原有的数据。
  • 幻读(Phantom Read):在一个事务的两次查询中数据笔数不一致,例若有一个事务查询了几列(Row)数据,而另外一个事务却在此时插入了新的几列数据,先前的事务在接下来的查询中,就会发现有几列数据是它先前所没有的。

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

为了达到事务的四大特性,数据库定义了4种不一样的事务隔离级别,由低到高依次为Read uncommitted、Read committed、Repeatable read、Serializable,这四个级别能够逐个解决脏读、不可重复读、幻读这几类问题。

SQL 标准定义了四个隔离级别:

  • READ-UNCOMMITTED(读取未提交): 最低的隔离级别,容许读取还没有提交的数据变动,可能会致使脏读、幻读或不可重复读。
  • READ-COMMITTED(读取已提交): 容许读取并发事务已经提交的数据,能够阻止脏读,可是幻读或不可重复读仍有可能发生。
  • REPEATABLE-READ(可重复读): 对同一字段的屡次读取结果都是一致的,除非数据是被自己事务本身所修改,能够阻止脏读和不可重复读,但幻读仍有可能发生。
  • SERIALIZABLE(可串行化): 最高的隔离级别,彻底服从ACID的隔离级别。全部的事务依次逐个执行,这样事务之间就彻底不可能产生干扰,也就是说,该级别能够防止脏读、不可重复读以及幻读。

这里须要注意的是:Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别

事务隔离机制的实现基于锁机制和并发调度。其中并发调度使用的是MVVC(多版本并发控制),经过保存修改的旧版本信息来支持并发一致性读和回滚等特性。

由于隔离级别越低,事务请求的锁越少,因此大部分数据库系统的隔离级别都是READ-COMMITTED(读取提交内容):,可是你要知道的是InnoDB 存储引擎默认使用 **REPEATABLE-READ(可重读)**并不会有任何性能损失。

InnoDB 存储引擎在 分布式事务 的状况下通常会用到**SERIALIZABLE(可串行化)**隔离级别。

Mysql执行流程

1.一条查询 SQL 语句是如何执行的

咱们的程序或者工具要操做数据库,第一步要作什么事情?

跟数据库创建链接。

1.1. 通讯协议

首先,MySQL 必需要运行一个服务,监听默认的 3306 端口。

在咱们开发系统跟第三方对接的时候,必需要弄清楚的有两件事。

第一个就是通讯协议,好比咱们是用 HTTP 仍是 WebService 仍是 TCP?

第二个是消息格式,好比咱们用 XML 格式,仍是 JSON 格式,仍是定长格式?报文头长度多少,包含什么内容,每一个字段的详细含义。好比咱们以前跟银联对接,银联的银行卡联网规范,约定了一种比较复杂的通信协议叫作:四进四出单工异步长链接(为了保证稳定性和性能)。

MySQL 是支持多种通讯协议的,能够使用同步/异步的方式,支持长链接/短链接。这里咱们拆分来看。第一个是通讯类型

通讯类型: 同步或者异步

同步通讯的特色:

一、同步通讯依赖于被调用方,受限于被调用方的性能。也就是说,应用操做数据库,线程会阻塞,等待数据库的返回。

二、通常只能作到一对一,很难作到一对多的通讯。

异步跟同步相反:

一、异步能够避免应用阻塞等待,可是不能节省 SQL 执行的时间。

二、若是异步存在并发,每个 SQL 的执行都要单独创建一个链接,避免数据混乱。可是这样会给服务端带来巨大的压力(一个链接就会建立一个线程,线程间切换会占用大量 CPU 资源)。另外异步通讯还带来了编码的复杂度,因此通常不建议使用。若是要异步,必须使用链接池,排队从链接池获取链接而不是建立新链接。

通常来讲咱们链接数据库都是同步链接

链接方式: 长链接或者短链接

MySQL 既支持短链接,也支持长链接。短链接就是操做完毕之后,立刻 close 掉。长链接能够保持打开,减小服务端建立和释放链接的消耗,后面的程序访问的时候还能够使用这个链接。通常咱们会在链接池中使用长链接。

保持长链接会消耗内存。长时间不活动的链接,MySQL 服务器会断开。

show global variables like 'wait_timeout'; -- 非交互式超时时间, 如 JDBC 程序
show global variables like 'interactive_timeout'; -- 交互式超时时间, 如数据库工具

默认都是 28800 秒,8 小时。

咱们怎么查看 MySQL 当前有多少个链接?

能够用 show status 命令:

show global status like 'Thread%';

Threads_cached:缓存中的线程链接数。

Threads_connected:当前打开的链接数。

Threads_created:为处理链接建立的线程数。

Threads_running:非睡眠状态的链接数,一般指并发链接数。

每产生一个链接或者一个会话,在服务端就会建立一个线程来处理。反过来,若是要杀死会话,就是 Kill 线程。

有了链接数,怎么知道当前链接的状态?
也能够使用 SHOW PROCESSLIST;(root 用户)查看 SQL 的执行状态。

一些常见的状态:

MySQL 服务容许的最大链接数是多少呢?

在 5.7 版本中默认是 151 个,最大能够设置成 16384(2^14)。

show variables like 'max_connections';

show 的参数说明:

一、级别:会话 session 级别(默认);全局 global 级别

二、动态修改:set,重启后失效;永久生效,修改配置文件/etc/my.conf

set global max_connections = 1000;

通讯协议

MySQL 支持哪些通讯协议呢?

第一种是 Unix Socket。

好比咱们在 Linux 服务器上,若是没有指定-h 参数,它就用 socket 方式登陆(省略了-S /var/lib/mysql/mysql.sock)。

它不用经过网络协议,也能够链接到 MySQL 的服务器,它须要用到服务器上的一个物理文件(/var/lib/mysql/mysql.sock)。

select @@socket;

若是指定-h 参数,就会用第二种方式,TCP/IP 协议。

mysql -h192.168.8.211 -uroot -p123456

我 们 的 编 程 语 言 的 连 接 模 块 都 是 用 TCP 协 议 连 接 到 MySQL 服 务 器 的 , 比 如

mysql-connector-java-x.x.xx.jar。

另外还有命名管道(Named Pipes)和内存共享(Share Memory)的方式,这两种通讯方式只能在 Windows 上面使用,通常用得比较少。

1.1.2.通讯方式

第二个是通讯方式。

单工:

在两台计算机通讯的时候,数据的传输是单向的。生活中的类比:遥控器。

半双工:

在两台计算机之间,数据传输是双向的,你能够给我发送,我也能够给你发送,

可是在这个通信链接里面,同一时间只能有一台服务器在发送数据,也就是你要给我发

的话,也必须等我发给你完了以后才能给我发。生活中的类比:对讲机。

全双工:

数据的传输是双向的,而且能够同时传输。生活中的类比:打电话。

MySQL 使用了半双工的通讯方式?

要么是客户端向服务端发送数据,要么是服务端向客户端发送数据,这两个动做不能同时发生。因此客户端发送 SQL 语句给服务端的时候,(在一次链接里面)数据是不能分红小块发送的,无论你的 SQL 语句有多大,都是一次性发送。

好比咱们用 MyBatis 动态 SQL 生成了一个批量插入的语句,插入 10 万条数据,values后面跟了一长串的内容,或者 where 条件 in 里面的值太多,会出现问题。

这个时候咱们必需要调整 MySQL 服务器配置 max_allowed_packet 参数的值(默认是 4M),把它调大,不然就会报错。

另外一方面,对于服务端来讲,也是一次性发送全部的数据,不能由于你已经取到了想要的数据就中断操做,这个时候会对网络和内存产生大量消耗。

因此,咱们必定要在程序里面避免不带 limit 的这种操做,好比一次把全部知足条件的数据所有查出来,必定要先 count 一下。若是数据量的话,能够分批查询。

执行一条查询语句,客户端跟服务端创建链接以后呢?下一步要作什么?

1.2. 查询缓存

MySQL 内部自带了一个缓存模块。

缓存的做用咱们应该很清楚了,把数据以 KV 的形式放到内存里面,能够加快数据的读取速度,也能够减小服务器处理的时间。可是 MySQL 的缓存咱们好像比较陌生,历来没有去配置过,也不知道它何时生效?

好比 user_innodb 有 500 万行数据,没有索引。咱们在没有索引的字段上执行一样的查询,你们以为第二次会快吗?

select * from user_innodb where name='javaHuang';

为什么缓存没有生效,为何?MySQL 的缓存默认是关闭的。

show variables like 'query_cache%';

默认关闭的意思就是不推荐使用,为何 MySQL 不推荐使用它自带的缓存呢?

主要是由于 MySQL 自带的缓存的应用场景有限,第一个是它要求 SQL 语句必须如出一辙,中间多一个空格,字母大小写不一样都被认为是不一样的的 SQL。

第二个是表里面任何一条数据发生变化的时候,这张表全部缓存都会失效,因此对于有大量数据更新的应用,也不适合。

因此缓存这一块,咱们仍是交给 ORM 框架(好比 MyBatis 默认开启了一级缓存),或者独立的缓存服务,好比 Redis 来处理更合适。

在 MySQL 8.0 中,查询缓存已经被移除了。

1.3. 语法解析和预处理(Parser & Preprocessor)

咱们没有使用缓存的话,就会跳过缓存的模块,下一步咱们要作什么呢?

OK,这里我会有一个疑问,为何个人一条 SQL 语句可以被识别呢?假如我随便执行一个字符串 penyuyan,服务器报了一个 1064 的错:

[Err] 1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for theright syntax to use near 'penyuyan' at line 1

它是怎么知道我输入的内容是错误的?

这个就是 MySQL 的 Parser 解析器和 Preprocessor 预处理模块。

这一步主要作的事情是对语句基于 SQL 语法进行词法和语法分析和语义的解析。

1.3.1.词法解析

词法分析就是把一个完整的 SQL 语句打碎成一个个的单词。

好比一个简单的 SQL 语句:

select name from user where id = 1;

它会打碎成 8 个符号,每一个符号是什么类型,从哪里开始到哪里结束。

1.3.2.语法解析

第二步就是语法分析,语法分析会对 SQL 作一些语法检查,好比单引号有没有闭合,而后根据 MySQL 定义的语法规则,根据 SQL 语句生成一个数据结构。这个数据结构咱们把它叫作解析树(select_lex)。

任何数据库的中间件,好比 Mycat,Sharding-JDBC(用到了 Druid Parser),都必需要有词法和语法分析功能,在市面上也有不少的开源的词法解析的工具(好比 LEX,Yacc)。

1.3.3.预处理器

问题:若是我写了一个词法和语法都正确的 SQL,可是表名或者字段不存在,会在哪里报错?是在数据库的执行层仍是解析器?好比:

select * from penyuyan;

解析器能够分析语法,可是它怎么知道数据库里面有什么表,表里面有什么字段呢?

实际上仍是在解析的时候报错,解析 SQL 的环节里面有个预处理器。

它会检查生成的解析树,解决解析器没法解析的语义。好比,它会检查表和列名是否存在,检查名字和别名,保证没有歧义。

预处理以后获得一个新的解析树。

1.4. 查询优化( Query Optimizer) 与查询执行计划

1.4.2. 优化器能够作什么?

MySQL 的优化器能处理哪些优化类型呢?

举两个简单的例子:

一、当咱们对多张表进行关联查询的时候,以哪一个表的数据做为基准表。

二、有多个索引能够使用的时候,选择哪一个索引。

实际上,对于每一种数据库来讲,优化器的模块都是必不可少的,他们经过复杂的算法实现尽量优化查询效率的目标。

若是对于优化器的细节感兴趣,能够看看《数据库查询优化器的艺术-原理解析与SQL性能优化》。

可是优化器也不是万能的,并非再垃圾的 SQL 语句都能自动优化,也不是每次都能选择到最优的执行计划,你们在编写 SQL 语句的时候仍是要注意。

若是咱们想知道优化器是怎么工做的,它生成了几种执行计划,每种执行计划的 cost是多少,应该怎么作?

1.4.3.优化器是怎么获得执行计划的?

首先咱们要启用优化器的追踪(默认是关闭的):

SHOW VARIABLES LIKE 'optimizer_trace';
set optimizer_trace='enabled=on';

注意开启这开关是会消耗性能的,由于它要把优化分析的结果写到表里面,因此不要轻易开启,或者查看完以后关闭它(改为 off)。

注意:参数分为 session 和 global 级别。

接着咱们执行一个 SQL 语句,优化器会生成执行计划:

select t.tcid from teacher t,teacher_contact tc where t.tcid = tc.tcid;

这个时候优化器分析的过程已经记录到系统表里面了,咱们能够查询:

select * from information_schema.optimizer_trace\G

它是一个 JSON 类型的数据,主要分红三部分,准备阶段、优化阶段和执行阶段。

expanded_query 是优化后的 SQL 语句。

considered_execution_plans 里面列出了全部的执行计划。

分析完记得关掉它:

set optimizer_trace="enabled=off";
SHOW VARIABLES LIKE 'optimizer_trace';

1.4.4. 优化器获得的结果

优化完以后,获得一个什么东西呢?

优化器最终会把解析树变成一个查询执行计划,查询执行计划是一个数据结构。

固然,这个执行计划是否是必定是最优的执行计划呢?不必定,由于 MySQL 也有可能覆盖不到全部的执行计划。

咱们怎么查看 MySQL 的执行计划呢?好比多张表关联查询,先查询哪张表?在执行查询的时候可能用到哪些索引,实际上用到了什么索引?

MySQL 提供了一个执行计划的工具。咱们在 SQL 语句前面加上 EXPLAIN,就能够看到执行计划的信息。

EXPLAIN select name from user where id=1

注意 Explain 的结果也不必定最终执行的方式。

1.5. 存储引擎

获得执行计划之后,SQL 语句是否是终于能够执行了?

问题又来了:

一、从逻辑的角度来讲,咱们的数据是放在哪里的,或者说放在一个什么结构里面?

二、执行计划在哪里执行?是谁去执行?

1.5.1. 存储引擎基本介绍

咱们先回答第一个问题:在关系型数据库里面,数据是放在什么结构里面的?(放在表 Table 里面的)

咱们能够把这个表理解成 Excel 电子表格的形式。因此咱们的表在存储数据的同时,还要组织数据的存储结构,这个存储结构就是由咱们的存储引擎决定的,因此咱们也能够把存储引擎叫作表类型。

在 MySQL 里面,支持多种存储引擎,他们是能够替换的,因此叫作插件式的存储引擎。为何要搞这么多存储引擎呢?一种还不够用吗?

这个问题先留着。

1.5.2. 查看存储引擎

好比咱们数据库里面已经存在的表,咱们怎么查看它们的存储引擎呢?

show table status from `ToBeTopJavaer`;

或者经过 DDL 建表语句来查看。

在 MySQL 里面,咱们建立的每一张表均可以指定它的存储引擎,而不是一个数据库只能使用一个存储引擎。存储引擎的使用是以表为单位的。并且,建立表以后还能够修改存储引擎。

咱们说一张表使用的存储引擎决定咱们存储数据的结构,那在服务器上它们是怎么存储的呢?咱们先要找到数据库存放数据的路径:

show variables like 'datadir';

默认状况下,每一个数据库有一个本身文件夹,以 ToBeTopJavaer数据库为例。

任何一个存储引擎都有一个 frm 文件,这个是表结构定义文件。

不一样的存储引擎存放数据的方式不同,产生的文件也不同,innodb 是 1 个,memory 没有,myisam 是两个。

这些存储引擎的差异在哪呢?

1.5.3. 存储引擎比较

常见存储引擎

MyISAM 和 InnoDB 是咱们用得最多的两个存储引擎,在 MySQL 5.5 版本以前,默认的存储引擎是 MyISAM,它是 MySQL 自带的。咱们建立表的时候不指定存储引擎,它就会使用 MyISAM 做为存储引擎。

MyISAM 的前身是 ISAM(Indexed Sequential Access Method:利用索引,顺序存取数据的方法)。

5.5 版本以后默认的存储引擎改为了 InnoDB,它是第三方公司为 MySQL 开发的。

为何要改呢?最主要的缘由仍是 InnoDB 支持事务,支持行级别的锁,对于业务一致性要求高的场景来讲更适合。

这个里面又有 Oracle 和 MySQL 公司的一段恩怨情仇。

InnoDB 原本是 InnobaseOy 公司开发的,它和 MySQL AB 公司合做开源了 InnoDB的代码。可是没想到 MySQL 的竞争对手 Oracle 把 InnobaseOy 收购了。后来 08 年 Sun 公司(开发 Java 语言的 Sun)收购了 MySQL AB,09 年 Sun 公司又被 Oracle 收购了,因此 MySQL,InnoDB 又是一家了。有人以为 MySQL 愈来愈像Oracle,其实也是这个缘由。

那么除了这两个咱们最熟悉的存储引擎,数据库还支持其余哪些经常使用的存储引擎呢?

数据库支持的存储引擎

咱们能够用这个命令查看数据库对存储引擎的支持状况:

show engines ;

其中有存储引擎的描述和对事务、XA 协议和 Savepoints 的支持。XA 协议用来实现分布式事务(分为本地资源管理器,事务管理器)。

Savepoints 用来实现子事务(嵌套事务)。建立了一个 Savepoints 以后,事务就能够回滚到这个点,不会影响到建立 Savepoints 以前的操做。

这些数据库支持的存储引擎,分别有什么特性呢?

MyISAM( 3 个文件)

These tables have a small footprint. Table-level locking limits the performance in read/write workloads, so it is often used in read-only or read-mostly workloads in Web and data warehousing configurations.

应用范围比较小。表级锁定限制了读/写的性能,所以在 Web 和数据仓库配置中,它一般用于只读或以读为主的工做。

特色:

1.支持表级别的锁(插入和更新会锁表)。不支持事务。

2.拥有较高的插入(insert)和查询(select)速度。

3.存储了表的行数(count 速度更快)。

(怎么快速向数据库插入 100 万条数据?咱们有一种先用 MyISAM 插入数据,而后修改存储引擎为 InnoDB 的操做。)

适合:只读之类的数据分析的项目。

InnoDB( 2 个文件)

The default storage engine in MySQL 5.7. InnoDB is a transaction-safe (ACID compliant) storage engine for MySQL that has commit, rollback, and crash-recovery capabilities to protect user data. InnoDB row-level locking (without escalation to coarser granularity locks) and Oracle-style consistent nonlocking reads increase multi-user concurrency and performance.

InnoDB stores user data in clustered indexes to reduce I/O for common queries based on primary keys. To maintain data integrity, InnoDB also supports FOREIGN KEY referential-integrity constraints.

mysql 5.7 中的默认存储引擎。InnoDB 是一个事务安全(与 ACID 兼容)的 MySQL存储引擎,它具备提交、回滚和崩溃恢复功能来保护用户数据。InnoDB 行级锁(不升级为更粗粒度的锁)和 Oracle 风格的一致非锁读提升了多用户并发性和性能。InnoDB 将用户数据存储在汇集索引中,以减小基于主键的常见查询的 I/O。为了保持数据完整性,InnoDB 还支持外键引用完整性约束。

特色:

1.支持事务,支持外键,所以数据的完整性、一致性更高。

2.支持行级别的锁和表级别的锁。

3.支持读写并发,写不阻塞读(MVCC)。

4.特殊的索引存放方式,能够减小 IO,提高查询效率。

适合:常常更新的表,存在并发读写或者有事务处理的业务系统。

Memory( 1 个文件)

Stores all data in RAM, for fast access in environments that require quick lookups of non-critical data. This engine was formerly known as the HEAP engine. Its use cases are decreasing; InnoDB with its buffer pool memory area provides a general-purpose and durable way to keep most or all data in memory, and NDBCLUSTER provides fast key-value lookups for huge distributed data sets.

将全部数据存储在 RAM 中,以便在须要快速查找非关键数据的环境中快速访问。这个引擎之前被称为堆引擎。其使用案例正在减小;InnoDB 及其缓冲池内存区域提供了一种通用、持久的方法来将大部分或全部数据保存在内存中,而 ndbcluster 为大型分布式数据集提供了快速的键值查找。

特色:

1.把数据放在内存里面,读写的速度很快,可是数据库重启或者崩溃,数据会所有消

失。只适合作临时表。

2.将表中的数据存储到内存中。

CSV( 3 个文件)

Its tables are really text files with comma-separated values. CSV tables let you import or dump data in CSV format, to exchange data with scripts and applications that read and write that same format. Because CSV tables are not indexed, you typically keep the data in InnoDB tables during normal operation, and only use CSV tables during the import or export stage.

它的表其实是带有逗号分隔值的文本文件。csv表容许以csv格式导入或转储数据,以便与读写相同格式的脚本和应用程序交换数据。由于 csv 表没有索引,因此一般在正常操做期间将数据保存在 innodb 表中,而且只在导入或导出阶段使用 csv 表。

特色:不容许空行,不支持索引。格式通用,能够直接编辑,适合在不一样数据库之间导入导出。

Archive( 2 个文件)

These compact, unindexed tables are intended for storing and retrieving large amounts of seldom-referenced historical,archived, or security audit information.

这些紧凑的未索引的表用于存储和检索大量不多引用的历史、存档或安全审计信息。

特色:不支持索引,不支持 update delete。

这是 MySQL 里面常见的一些存储引擎,咱们看到了,不一样的存储引擎提供的特性都不同,它们有不一样的存储机制、索引方式、锁定水平等功能。

咱们在不一样的业务场景中对数据操做的要求不一样,就能够选择不一样的存储引擎来知足咱们的需求,这个就是 MySQL 支持这么多存储引擎的缘由。

1.5.4. 如何选择存储引擎?

若是对数据一致性要求比较高,须要事务支持,能够选择 InnoDB。

若是数据查询多更新少,对查询性能要求比较高,能够选择 MyISAM。

若是须要一个用于查询的临时表,能够选择 Memory。

若是全部的存储引擎都不能知足你的需求,而且技术能力足够,能够根据官网内部手册用 C 语言开发一个存储引擎:https://dev.mysql.com/doc/internals/en/custom-eng

1.6. 执行引擎( Query Execution Engine) , 返回结果

OK,存储引擎分析完了,它是咱们存储数据的形式,继续第二个问题,是谁使用执行计划去操做存储引擎呢?

这就是咱们的执行引擎,它利用存储引擎提供的相应的 API 来完成操做。

为何咱们修改了表的存储引擎,操做方式不须要作任何改变?由于不一样功能的存储引擎实现的 API 是相同的。

最后把数据返回给客户端,即便没有结果也要返回。

2. MySQL 体系结构总结

基于上面分析的流程,咱们一块儿来梳理一下 MySQL 的内部模块。

2.1. 模块详解

一、 Connector:用来支持各类语言和 SQL 的交互,好比 PHP,Python,Java 的JDBC;

二、 Management Serveices & Utilities:系统管理和控制工具,包括备份恢复、MySQL 复制、集群等等;

三、 Connection Pool:链接池,管理须要缓冲的资源,包括用户密码权限线程等等;

四、 SQL Interface:用来接收用户的 SQL 命令,返回用户须要的查询结果

五、 Parser:用来解析 SQL 语句;

六、 Optimizer:查询优化器;

七、 Cache and Buffer:查询缓存,除了行记录的缓存以外,还有表缓存,Key 缓存,权限缓存等等;

八、 Pluggable Storage Engines:插件式存储引擎,它提供 API 给服务层使用,跟具体的文件打交道。

2.2. 架构分层

整体上,咱们能够把 MySQL 分红三层,跟客户端对接的链接层,真正执行操做的服务层,和跟硬件打交道的存储引擎层(参考 MyBatis:接口、核心、基础)。

2.1.1.链接层

咱们的客户端要链接到 MySQL 服务器 3306 端口,必需要跟服务端创建链接,那么管理全部的链接,验证客户端的身份和权限,这些功能就在链接层完成。

2.1.2.服务层

链接层会把 SQL 语句交给服务层,这里面又包含一系列的流程:

好比查询缓存的判断、根据 SQL 调用相应的接口,对咱们的 SQL 语句进行词法和语法的解析(好比关键字怎么识别,别名怎么识别,语法有没有错误等等)。而后就是优化器,MySQL 底层会根据必定的规则对咱们的 SQL 语句进行优化,最后再交给执行器去执行。

2.1.3.存储引擎

存储引擎就是咱们的数据真正存放的地方,在 MySQL 里面支持不一样的存储引擎。再往下就是内存或者磁盘。

3. 一条更新 SQL 是如何执行的?

讲完了查询流程,咱们是否是再讲讲更新流程、插入流程和删除流程?

在数据库里面,咱们说的 update 操做其实包括了更新、插入和删除。若是你们有看过 MyBatis 的源码,应该知道 Executor 里面也只有 doQuery()和 doUpdate()的方法,没有 doDelete()和 doInsert()。

更新流程和查询流程有什么不一样呢?

基本流程也是一致的,也就是说,它也要通过解析器、优化器的处理,最后交给执行器。

区别就在于拿到符合条件的数据以后的操做。

3.1. 缓冲池 Buffer Pool

首先,InnnoDB 的数据都是放在磁盘上的,InnoDB 操做数据有一个最小的逻辑单位,叫作页(索引页和数据页)。咱们对于数据的操做,不是每次都直接操做磁盘,由于磁盘的速度太慢了。InnoDB 使用了一种缓冲池的技术,也就是把磁盘读到的页放到一块内存区域里面。这个内存区域就叫 Buffer Pool。下一次读取相同的页,先判断是否是在缓冲池里。

下一次读取相同的页,先判断是否是在缓冲池里面,若是是,就直接读取,不用再次访问磁盘。

修改数据的时候,先修改缓冲池里面的页。内存的数据页和磁盘数据不一致的时候,咱们把它叫作脏页。InnoDB 里面有专门的后台线程把 Buffer Pool 的数据写入到磁盘,每隔一段时间就一次性地把多个修改写入磁盘,这个动做就叫作刷脏。

Buffer Pool 是 InnoDB 里面很是重要的一个结构,它的内部又分红几块区域。这里咱们趁机到官网来认识一下 InnoDB 的内存结构和磁盘结构。

3.2. InnoDB 内存结构和磁盘结构

3.3.1.内存结构

Buffer Pool 主要分为 3 个部分: Buffer Pool、Change Buffer、Adaptive Hash Index,另外还有一个(redo)log buffer。

一、 Buffer Pool

Buffer Pool 缓存的是页面信息,包括数据页、索引页。

查看服务器状态,里面有不少跟 Buffer Pool 相关的信息:

SHOW STATUS LIKE '%innodb_buffer_pool%';

这些状态均可以在官网查到详细的含义,用搜索功能。

Buffer Pool 默认大小是 128M(134217728 字节),能够调整。

查看参数(系统变量):

SHOW VARIABLES like '%innodb_buffer_pool%';

这些参数均可以在官网查到详细的含义,用搜索功能。

内存的缓冲池写满了怎么办?(Redis 设置的内存满了怎么办?)InnoDB 用 LRU算法来管理缓冲池(链表实现,不是传统的 LRU,分红了 young 和 old),通过淘汰的数据就是热点数据。

内存缓冲区对于提高读写性能有很大的做用。思考一个问题:

当须要更新一个数据页时,若是数据页在 Buffer Pool 中存在,那么就直接更新好了。不然的话就须要从磁盘加载到内存,再对内存的数据页进行操做。也就是说,若是没有命中缓冲池,至少要产生一次磁盘 IO,有没有优化的方式呢?

二、 Change Buffer 写缓冲

若是这个数据页不是惟一索引,不存在数据重复的状况,也就不须要从磁盘加载索引页判断数据是否是重复(惟一性检查)。这种状况下能够先把修改记录在内存的缓冲池中,从而提高更新语句(Insert、Delete、Update)的执行速度。

这一块区域就是 Change Buffer。5.5 以前叫 Insert Buffer 插入缓冲,如今也能支持 delete 和 update。

最后把 Change Buffer 记录到数据页的操做叫作 merge。何时发生 merge?有几种状况:在访问这个数据页的时候,或者经过后台线程、或者数据库 shut down、redo log 写满时触发。

若是数据库大部分索引都是非惟一索引,而且业务是写多读少,不会在写数据后马上读取,就能够使用 Change Buffer(写缓冲)。写多读少的业务,调大这个值:

SHOW VARIABLES LIKE 'innodb_change_buffer_max_size';

表明 Change Buffer 占 Buffer Pool 的比例,默认 25%。

三、 Adaptive Hash Index

索引应该是放在磁盘的,为何要专门把一种哈希的索引放到内存?好好思考。

四、 ( redo) Log Buffer

思考一个问题:若是 Buffer Pool 里面的脏页尚未刷入磁盘时,数据库宕机或者重启,这些数据丢失。若是写操做写到一半,甚至可能会破坏数据文件致使数据库不可用。

为了不这个问题,InnoDB 把全部对页面的修改操做专门写入一个日志文件,而且在数据库启动时从这个文件进行恢复操做(实现 crash-safe)——用它来实现事务的持久性。

这个文件就是磁盘的 redo log(叫作重作日志),对应于/var/lib/mysql/目录下的ib_logfile0 和 ib_logfile1,每一个 48M。

这 种 日 志 和 磁 盘 配 合 的 整 个 过 程 , 其 实 就 是 MySQL 里 的 WAL 技 术(Write-Ahead Logging),它的关键点就是先写日志,再写磁盘。

show variables like 'innodb_log%';

问题:

一样是写磁盘,为何不直接写到 db file 里面去?为何先写日志再写磁盘?

咱们先来了解一下随机 I/O 和顺序 I/O 的概念。

磁盘的最小组成单元是扇区,一般是 512 个字节。

操做系统和内存打交道,最小的单位是页 Page。

操做系统和磁盘打交道,读写磁盘,最小的单位是块 Block。

若是咱们所须要的数据是随机分散在不一样页的不一样扇区中,那么找到相应的数据须要等到磁臂旋转到指定的页,而后盘片寻找到对应的扇区,才能找到咱们所须要的一块数据,一次进行此过程直到找完全部数据,这个就是随机 IO,读取数据速度较慢。

假设咱们已经找到了第一块数据,而且其余所需的数据就在这一块数据后边,那么就不须要从新寻址,能够依次拿到咱们所需的数据,这个就叫顺序 IO。

刷盘是随机 I/O,而记录日志是顺序 I/O,顺序 I/O 效率更高。所以先把修改写入日志,能够延迟刷盘时机,进而提高系统吞吐。

固然 redo log 也不是每一次都直接写入磁盘,在 Buffer Pool 里面有一块内存区域(Log Buffer)专门用来保存即将要写入日志文件的数据,默认 16M,它同样能够节省磁盘 IO。

SHOW VARIABLES LIKE 'innodb_log_buffer_size';

须要注意:redo log 的内容主要是用于崩溃恢复。磁盘的数据文件,数据来自 buffer pool。redo log 写入磁盘,不是写入数据文件。

那么,Log Buffer 何时写入 log file?

在咱们写入数据到磁盘的时候,操做系统自己是有缓存的。flush 就是把操做系统缓冲区写入到磁盘。

log buffer 写入磁盘的时机,由一个参数控制,默认是 1。

SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';

这是内存结构的第 4 块内容,redo log,它又分红内存和磁盘两部分。redo log 有什么特色?

一、redo log 是 InnoDB 存储引擎实现的,并非全部存储引擎都有。

二、不是记录数据页更新以后的状态,而是记录这个页作了什么改动,属于物理日志。

三、redo log 的大小是固定的,前面的内容会被覆盖。

check point 是当前要覆盖的位置。若是 write pos 跟 check point 重叠,说明 redo log 已经写满,这时候须要同步 redo log 到磁盘中。

这是 MySQL 的内存结构,总结一下,分为:

Buffer pool、change buffer、Adaptive Hash Index、 log buffer。

磁盘结构里面主要是各类各样的表空间,叫作Table space

3.3.2.磁盘结构

表空间能够看作是 InnoDB 存储引擎逻辑结构的最高层,全部的数据都存放在表空间中。InnoDB 的表空间分为 5 大类。

系统表空间 system tablespace

在默认状况下 InnoDB 存储引擎有一个共享表空间(对应文件/var/lib/mysql/ibdata1),也叫系统表空间。

InnoDB 系统表空间包含 InnoDB 数据字典和双写缓冲区,Change Buffer 和 Undo Logs),若是没有指定 file-per-table,也包含用户建立的表和索引数据。

一、undo 在后面介绍,由于有独立的表空间。

二、数据字典:由内部系统表组成,存储表和索引的元数据(定义信息)。

三、双写缓冲(InnoDB 的一大特性):

InnoDB 的页和操做系统的页大小不一致,InnoDB 页大小通常为 16K,操做系统页大小为 4K,InnoDB 的页写入到磁盘时,一个页须要分 4 次写。

若是存储引擎正在写入页的数据到磁盘时发生了宕机,可能出现页只写了一部分的状况,好比只写了 4K,就宕机了,这种状况叫作部分写失效(partial page write),可能会致使数据丢失。

show variables like 'innodb_doublewrite';

咱们不是有 redo log 吗?可是有个问题,若是这个页自己已经损坏了,用它来作崩溃恢复是没有意义的。因此在对于应用 redo log 以前,须要一个页的副本。若是出现了写入失效,就用页的副原本还原这个页,而后再应用 redo log。这个页的副本就是 double write,InnoDB 的双写技术。经过它实现了数据页的可靠性。

跟 redo log 同样,double write 由两部分组成,一部分是内存的 double write,一个部分是磁盘上的 double write。由于 double write 是顺序写入的,不会带来很大的开销。

在默认状况下,全部的表共享一个系统表空间,这个文件会愈来愈大,并且它的空间不会收缩。

独占表空间 file-per-table tablespaces

咱们可让每张表独占一个表空间。这个开关经过 innodb_file_per_table 设置,默认开启。

SHOW VARIABLES LIKE 'innodb_file_per_table';

开启后,则每张表会开辟一个表空间,这个文件就是数据目录下的 ibd 文件(例如/var/lib/mysql/gupao/user_innodb.ibd),存放表的索引和数据。

可是其余类的数据,如回滚(undo)信息,插入缓冲索引页、系统事务信息,二次写缓冲(Double write buffer)等仍是存放在原来的共享表空间内。

通用表空间 general tablespaces

通用表空间也是一种共享的表空间,跟 ibdata1 相似。

能够建立一个通用的表空间,用来存储不一样数据库的表,数据路径和文件能够自定义。语法:

create tablespace ts2673 add datafile '/var/lib/mysql/ts2673.ibd' file_block_size=16K engine=innodb;

在建立表的时候能够指定表空间,用 ALTER 修改表空间能够转移表空间。

create table t2673(id integer) tablespace ts2673;

不一样表空间的数据是能够移动的。

删除表空间须要先删除里面的全部表:

drop table t2673;
drop tablespace ts2673;

临时表空间 temporary tablespaces

存储临时表的数据,包括用户建立的临时表,和磁盘的内部临时表。对应数据目录下的 ibtmp1 文件。当数据服务器正常关闭时,该表空间被删除,下次从新产生。

Redo log

磁盘结构里面的 redo log,在前面已经介绍过了。

undo log tablespace

undo log(撤销日志或回滚日志)记录了事务发生以前的数据状态(不包括 select)。若是修改数据时出现异常,能够用 undo log 来实现回滚操做(保持原子性)。

在执行 undo 的时候,仅仅是将数据从逻辑上恢复至事务以前的状态,而不是从物理页面上操做实现的,属于逻辑格式的日志。

redo Log 和 undo Log 与事务密切相关,统称为事务日志。

undo Log 的数据默认在系统表空间 ibdata1 文件中,由于共享表空间不会自动收缩,也能够单首创建一个 undo 表空间。

show global variables like '%undo%';

有了这些日志以后,咱们来总结一下一个更新操做的流程,这是一个简化的过程。

name 原值是 javaHuang。

update user set name = 'penyuyan' where id=1;

一、事务开始,从内存或磁盘取到这条数据,返回给 Server 的执行器;

二、执行器修改这一行数据的值为 penyuyan;

三、记录 name=qingshan 到 undo log;

四、记录 name=penyuyan 到 redo log;

五、调用存储引擎接口,在内存(Buffer Pool)中修改 name=penyuyan;

六、事务提交。

内存和磁盘之间,工做着不少后台线程。

3.3.3.后台线程

(供了解)

后台线程的主要做用是负责刷新内存池中的数据和把修改的数据页刷新到磁盘。后台线程分为:master thread,IO thread,purge thread,page cleaner thread。

master thread 负责刷新缓存数据到磁盘并协调调度其它后台进程。

IO thread 分为 insert buffer、log、read、write 进程。分别用来处理 insert buffer、重作日志、读写请求的 IO 回调。

purge thread 用来回收 undo 页。

page cleaner thread 用来刷新脏页。

除了 InnoDB 架构中的日志文件,MySQL 的 Server 层也有一个日志文件,叫作binlog,它能够被全部的存储引擎使用。

3.3. Binlog

binlog 以事件的形式记录了全部的 DDL 和 DML 语句(由于它记录的是操做而不是数据值,属于逻辑日志),能够用来作主从复制和数据恢复。

跟 redo log 不同,它的文件内容是能够追加的,没有固定大小限制。

在开启了 binlog 功能的状况下,咱们能够把 binlog 导出成 SQL 语句,把全部的操做重放一遍,来实现数据的恢复。

binlog 的另外一个功能就是用来实现主从复制,它的原理就是从服务器读取主服务器的 binlog,而后执行一遍。

配置方式和主从复制的实现原理在后续会有专文详解。

有了这两个日志以后,咱们来看一下一条更新语句是怎么执行的:

例如一条语句:update teacher set name='盆鱼宴' where id=1;

一、先查询到这条数据,若是有缓存,也会用到缓存。

二、把 name 改为盆鱼宴,而后调用引擎的 API 接口,写入这一行数据到内存,同时记录 redo log。这时 redo log 进入 prepare 状态,而后告诉执行器,执行完成了,能够随时提交。

三、执行器收到通知后记录 binlog,而后调用存储引擎接口,设置 redo log为 commit状态。

四、更新完成。

这张图片的重点:

一、先记录到内存,再写日志文件。

二、记录 redo log 分为两个阶段。

三、存储引擎和 Server 记录不一样的日志。

三、先记录 redo,再记录 binlog。

Mysql锁

咱们知道,数据也是一种供许多用户共享访问的资源。如何保证数据并发访问的一致性、有效性,是全部数据库必须解决的一个问题,锁的冲突也是影响数据库并发访问性能的一个重要因素。从这一角度来讲,锁对于数据库而言就显得尤其重要。本文将带领你们一块儿深刻领略Mysql锁的各类风采。

表锁

表级锁是mysql锁中粒度最大的一种锁,表示当前的操做对整张表加锁,资源开销比行锁少,不会出现死锁的状况,可是发生锁冲突的几率很大。

该锁定机制最大的特色是实现逻辑很是简单,带来的系统负面影响最小。因此获取锁和释放锁的速度很快。因为表级锁一次会将整个表锁定,因此能够很好的避免困扰咱们的死锁问题。表锁被大部分的mysql引擎支持,MyISAM和InnoDB都支持表级锁。

MyISAM只是支持表锁,所以性能相对Innodb来讲相对下降,而Innodb也支持表锁,可是默认的行锁,并且只有在查询或者其余SQL语句经过索引才会使用行锁。

行锁

行锁的是mysql锁中粒度最小的一种锁,由于锁的粒度很小,因此发生资源争抢的几率也最小,并发性能最大,可是也会形成死锁,每次加锁和释放锁的开销也会变大。

目前主要是Innodb使用行锁,Innodb也是mysql在5.5.5版本以后默认使用的存储引擎。

行锁按照使用方式也分为共享锁(S锁或者读锁)和排它锁(X锁或者写锁)

共享锁(S锁,读锁)

使用说明:若事务A对数据对象1加上S锁,则事务A能够读数据对象1但不能修改,其余事务只能再对数据对象1加S锁,而不能加X锁,直到事务A释放数据对象1上的S锁。这保证了其余事务能够读数据对象1,但在事务A释放数据对象1上的S锁以前不能对数据对象1作任何修改。

用法:

select ... lock in share mode;

共享锁就是多个事务对于同一数据能够共享一把锁,都能访问到数据,可是只能读不能修改。

排它锁(X锁,写锁)

使用说明:若事务A对数据对象1加上X锁,事务A能够读数据对象1也能够修改数据对象1,其余事务不能再对数据对象1加任何锁,直到事务A释放数据对象1上的锁。这保证了其余事务在事务A释放数据对象1上的锁以前不能再读取和修改数据对象1。

select ... for update

排他锁就是不能与其余所并存,如一个事务获取了一个数据行的排他锁,其余事务就不能再获取该行的其余锁

意向共享锁(IS)和意向排它锁(IX)

释义:

意向共享锁(IS):事务想要在得到表中某些记录的共享锁,须要在表上先加意向共享锁。

意向互斥锁(IX):事务想要在得到表中某些记录的互斥锁,须要在表上先加意向互斥锁。

意向共享锁和意向排它锁总称为意向锁。意向锁的出现是为了支持Innodb支持多粒度锁。

首先,意向锁是表级别锁。

理由:当咱们须要给一个加表锁的时候,咱们须要根据意向锁去判断表中有没有数据行被锁定,以肯定是否能加成功。若是意向锁是行锁,那么咱们就得遍历表中全部数据行来判断。若是意向锁是表锁,则咱们直接判断一次就知道表中是否有数据行被锁定了。因此说将意向锁设置成表级别的锁的性能比行锁高的多。

有了意向锁以后,前面例子中的事务A在申请行锁(写锁)以前,数据库会自动先给事务A申请表的意向排他锁。当事务B去申请表的写锁时就会失败,由于表上有意向排他锁以后事务B申请表的写锁时会被阻塞。

因此,意向锁的做用就是:

当一个事务在须要获取资源的锁定时,若是该资源已经被排他锁占用,则数据库会自动给该事务申请一个该表的意向锁。若是本身须要一个共享锁定,就申请一个意向共享锁。若是须要的是某行(或者某些行)的排他锁定,则申请一个意向排他锁。

乐观锁

乐观锁不是数据库自带的,须要咱们本身去实现。乐观锁是指操做数据库时(更新操做),想法很乐观,认为此次的操做不会致使冲突,在操做数据时,并不进行任何其余的特殊处理(也就是不加锁),而在进行更新后,再去判断是否有冲突了。

一般实现是这样的:在表中的数据进行操做时(更新),先给数据表加一个版本(version)字段,每操做一次,将那条记录的版本号加1。也就是先查询出那条记录,获取出version字段,若是要对那条记录进行操做(更新),则先判断此刻version的值是否与刚刚查询出来时的version的值相等,若是相等,则说明这段期间,没有其余程序对其进行操做,则能够执行更新,将version字段的值加1;若是更新时发现此刻的version值与刚刚获取出来的version的值不相等,则说明这段期间已经有其余程序对其进行操做了,则不进行更新操做。

使用实例:

1. SELECT data AS old_data, version AS old_version FROM …;
2. 根据获取的数据进行业务操做,获得new_data和new_version
3. UPDATE SET data = new_data, version = new_version WHERE version = old_version
if (updated row > 0) {
// 乐观锁获取成功,操做完成
} else {
// 乐观锁获取失败,回滚并重试
}

优势:从上面的例子能够看出,乐观锁机制避免了长事务中的数据库加锁开销,大大提高了大并发量下的系统总体性能表现。

缺点:乐观锁机制每每基于系统中的数据存储逻辑,所以也具有必定的局限性,如在上例中,因为乐观锁机制是在咱们的系统中实现,来自外部系统的更新操做不受咱们系统的控制,所以可能会形成脏数据被更新到数据库中。在系统设计阶段,应该充分考虑到这些状况出现的可能性,并进行相应调整(如将乐观锁策略在数据库存储过程当中实现,对外只开放基于此存储过程的数据更新途径,而不是将数据库表直接对外公开)。

总结:读用乐观锁,写用悲观锁。

悲观锁

悲观锁介绍(引自百科):

悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其余事务,以及来自外部系统的事务处理)修改持保守态度,所以,在整个数据处理过程当中,将数据处于锁定状态。悲观锁的实现,每每依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,不然,即便在本系统中实现了加锁机制,也没法保证外部系统不会修改数据)

悲观锁的实现:首先实现悲观锁时,咱们必须先使用

set autocommit=0; 关闭
mysql的autoCommit属性。

由于咱们查询出数据以后就要将该数据锁定。

关闭自动提交后,咱们须要手动开启事务。

//1.开始事务
begin; 或者 start transaction;
//2.查询出商品信息,而后经过for update锁定数据防止其余事务修改
select status from t_goods where id=1 for update;
//3.根据商品信息生成订单
insert into t_orders (id,goods_id) values (null,1);
//4.修改商品status为2
update t_goods set status=2;
//4.提交事务
commit; --执行完毕,提交事务

上述就实现了悲观锁,悲观锁就是悲观主义者,它会认为咱们在事务A中操做数据1的时候,必定会有事务B来修改数据1,因此,在第2步咱们将数据查询出来后直接加上排它锁(X)锁,防止别的事务来修改事务1,直到咱们commit后,才释放了排它锁。

优势:保证了数据处理时的安全性。

缺点:加锁形成了开销增长,而且增长了死锁的机会。下降了并发性。

乐观锁更新有可能会失败,甚至是更新几回都失败,这是有风险的。

因此若是写入居多,对吞吐要求不高,可以使用悲观锁。

下面三种锁都是innodb的行锁,前面咱们说过行锁是基于索引实现的,一旦加锁操做没有操做在索引上,就会退化成表锁。

间隙锁(Next-Key锁)

间隙锁,做用于非惟一索引上,主要目的,就是为了防止其余事务在间隔中插入数据,以致使“不可重复读”。

若是把事务的隔离级别降级为读提交(Read Committed, RC),间隙锁则会自动失效。

如图:(1,4),(4,7),(7,11),(11,∞)即为间隙锁要锁定的位置。

举例说明:

SELECT * FROM table WHERE id = 8 FOR UPDATE;
----此时,(7,11)就会被锁定
SELECT * FROM table WHERE id BETWEN 2 AND 5 FOR UPDATE;
----此时,(1,4)和(4,7)就会被锁定

记录锁

记录锁,它封锁索引记录,做用于惟一索引上,以下图所示:

select * from t where id=4 for update;

它会在id=4的索引记录上加锁,以阻止其余事务插入,更新,删除id=1的这一行。

须要说明的是:

select * from t where id=4;

则是快照读(SnapShot Read),它并不加锁,不影响其余事务操做该数据。

临键锁

临键锁,做用于非惟一索引上,是记录锁与间隙锁的组合,以下图所示:

它的封锁范围,既包含索引记录,又包含索引以前的区间,即(负无穷大,1],(2,4],(5,7],(8,11],(12,无穷大]。

在事务A中执行:

UPDATE table SET name = 'javaHuang' WHERE age = 4;
SELECT * FROM table WHERE age = 4 FOR UPDATE;

这两个语句都会锁定(2,4],(4,7)这两个区间。

即, InnoDB 会获取该记录行的 临键锁 ,并同时获取该记录行下一个区间的间隙锁。

临键锁的出现是为了innodb在rr隔离级别下,解决幻读问题(如何解决幻读问题,后续会出文章详细解答,也能够关注公众号【ToBeTopJavaer】,查看)。

若是把事务的隔离级别降级为RC,临键锁则也会失效。

死锁

释义:死锁是指两个或两个以上事务在执行过程当中因争抢锁资源而形成的互相等待的现象

上图所示,即为死锁产生的常规情景。

那么如何解决死锁?

1.等待事务超时,主动回滚。

2.进行死锁检查,主动回滚某条事务,让别的事务能继续走下去。

下面提供一种方法,解决死锁的状态:

SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;--查看正在被锁的事务

kill trx_mysql_thread_id;--(上图trx_mysql_thread_id列的值)

死锁是一个很复杂的话题,此处只能简而言之,后续会写一篇专门讲解死锁的文章。

咱们大体了解了mysql大部分锁的功能,做用,实现以及解决方法,我想作为了一个java开发工程师,了解到这个程度应该已经够了,毕竟咱们不是DBA,否则了解太深,抢了DBA的饭碗可就不太好了,开个玩笑,毕竟学无止境。

经过慢查询日志进行SQL分析和优化

咱们知道一条sql语句的执行过程当中会通过优化器进行优化。优化器就是对咱们的 SQL 语句进行分析,生成执行计划。问题:在咱们作项目的时候,有时会收到 DBA 的邮件,里面列出了咱们项目上几个耗时比较长的查询语句,让咱们去优化,这些语句是从哪里来的呢?咱们的服务层天天执行了这么多 SQL 语句,它怎么知道哪些 SQL 语句比较慢呢?第一步,咱们要把 SQL 执行状况记录下来。

1.1 慢查询日志 slow query log

1.1.1 打开慢日志开关由于开启慢查询日志是有代价的(跟 bin log、optimizer-trace 同样),因此它默认是关闭的:

show variables like 'slow_query%';

除了这个开关,还有一个参数,控制执行超过多长时间的 SQL 才记录到慢日志,默认是 10 秒。

show variables like '%slow_query%';

能够直接动态修改参数(重启后失效)。

set @@global.slow_query_log=1; set @@global.long_query_time=3; show variables like '%long_query%';-- 1 开启, 0 关闭, 重启后失效-- mysql 默认的慢查询时间是 10 秒, 另开一个窗口后才会查到最新值
show variables like '%slow_query%';

或者修改配置文件 my.cnf。如下配置定义了慢查询日志的开关、慢查询的时间、日志文件的存放路径。

slow_query_log = ON
long_query_time=2slow_query_log_file =/var/lib/mysql/localhost-slow.log

模拟慢查询:

select sleep(10);

查询 user_innodb 表的 500 万数据(检查是否是没有索引)。

SELECT * FROM `user_innodb` where phone = '136';

4.1.2 慢日志分析

一、 日志内容

show global status like 'slow_queries'; -- 查看有多少慢查询
show variables like '%slow_query%'; -- 获取慢日志目录

--日志路径
cat /var/lib/mysql/ localhost-slow.log

有了慢查询日志,怎么去分析统计呢?好比 SQL 语句的出现的慢查询次数最多,平均每次执行了多久?

二、 mysqldumpslowhttps://dev.mysql.com/doc/refman/5.7/en/mysqldumpslow.htmlMySQL提供了 mysqldumpslow 的工具,在 MySQL 的 bin 目录下。

mysqldumpslow --help

例如:查询用时最多的 20 条慢 SQL:

mysqldumpslow -s t -t 20 -g 'select' /var/lib/mysql/localhost-slow.log

Count表明这个 SQL 执行了多少次;Time表明执行的时间,括号里面是累计时间;Lock表示锁定的时间,括号是累计;Rows表示返回的记录数,括号是累计。除了慢查询日志以外,还有一个SHOW PROFILE工具能够使用。

4.2 SHOW PROFILE

SHOW PROFILE 是谷歌高级架构师 Jeremy Cole 贡献给 MySQL 社区的,能够查看SQL 语句执行的时候使用的资源,好比 CPU、IO 的消耗状况。在 SQL 中输入 help profile 能够获得详细的帮助信息。4.2.1 查看是否开启

select @@profiling;set @@profiling=1;

4.2.2 查看 profile 统计(命令最后带一个 s)

show profiles;

查看最后一个 SQL 的执行详细信息,从中找出耗时较多的环节(没有 s)。

show profile;

6.2E-5,小数点左移 5 位,表明 0.000062 秒。也能够根据 ID 查看执行详细信息,在后面带上 for query + ID。

show profile for query 1;

除了慢日志和 show profile,若是要分析出当前数据库中执行的慢的 SQL,还能够经过查看运行线程状态和服务器运行信息、存储引擎信息来分析。4.2.3 其余系统命令show processlist 运行线程

show processlist;

这是很重要的一个命令,用于显示用户运行线程。能够根据 id 号 kill 线程。也能够查表,效果同样:

select * from information_schema.processlist;

show status 服务器运行状态https://dev.mysql.com/doc/refman/5.7/en/show-status.htmlSHOWSTATUS 用于查看 MySQL 服务器运行状态(重启后会清空),有 session和 global 两种做用域,格式:参数-值。能够用 like 带通配符过滤。

SHOW GLOBAL STATUS LIKE 'com_select'; -- 查看 select 次数

show engine 存储引擎运行信息https://dev.mysql.com/doc/refman/5.7/en/show-engine.htmlshowengine 用来显示存储引擎的当前运行信息,包括事务持有的表锁、行锁信息;事务的锁等待状况;线程信号量等待;文件 IO 请求;buffer pool 统计信息。例如:

show engine innodb status;

若是须要将监控信息输出到错误信息 error log 中(15 秒钟一次),能够开启输出。

show variables like 'innodb_status_output%';-- 开启输出:
SET GLOBAL innodb_status_output=ON;SET GLOBAL innodb_status_output_locks=ON;

咱们如今已经知道了这么多分析服务器状态、存储引擎状态、线程运行信息的命令,若是让你去写一个数据库监控系统,你会怎么作?其实不少开源的慢查询日志监控工具,他们的原理其实也都是读取的系统的变量和状态。

如今咱们已经知道哪些 SQL 慢了,为何慢呢?慢在哪里?MySQL 提供了一个执行计划的工具(在架构中咱们有讲到,优化器最终生成的就是一个执行计划),其余数据库,例如 Oracle 也有相似的功能。经过 EXPLAIN 咱们能够模拟优化器执行 SQL 查询语句的过程,来知道 MySQL 是怎么处理一条 SQL 语句的。经过这种方式咱们能够分析语句或者表的性能瓶颈。explain 能够分析 update、delete、insert 么?MySQL 5.6.3之前只能分析 SELECT;MySQL5.6.3之后就能够分析update、delete、insert 了。

Sql优化

写SQL语句的时候咱们每每关注的是SQL的执行结果,可是是否真的关注了SQL的执行效率,是否注意了SQL的写法规范?

如下的干货分享是在实际开发过程当中总结的,但愿对你们有所帮助!

1. limit分页优化

当偏移量特别大时,limit效率会很是低。

SELECT id FROM A LIMIT 1000,10 很快

SELECT id FROM A LIMIT 90000,10 很慢

方案一

select id from A order by id limit 90000,10;

若是咱们结合order by使用。很快,0.04秒就OK。 由于使用了id主键作索引!固然,是否可以使用索引还须要根据业务逻辑来定,这里只是为了提醒你们,在分页的时候还需谨慎使用!

方案二

select id from A order by id between 90000 and 90010;

2.利用limit 1 、top 1 取得一行

有些业务逻辑进行查询操做时(特别是在根据某一字段DESC,取最大一笔).能够使用limit 1 或者 top 1 来终止[数据库索引]继续扫描整个表或索引。

反例

SELECT id FROM A LIKE 'abc%'

正例

SELECT id FROM A LIKE 'abc%' limit 1

3. 任何状况都不要用 select * from table ,用具体的字段列表替换"*",不要返回用不到的字段,避免全盘扫描!

反例

SELECT * FROM A

正例

SELECT id FROM A

4. 批量插入优化

反例

INSERT into person(name,age) values('A',24)
INSERT into person(name,age) values('B',24)
INSERT into person(name,age) values('C',24)

正例

INSERT into person(name,age) values('A',24),('B',24),('C',24),
sql语句的优化主要在于对索引的正确使用,而咱们在开发中常常犯的错误即是对表进行全盘扫描,一来影响性能,而来耗费时间!

5.like语句的优化

反例

SELECT id FROM A WHERE name like '%abc%'

因为abc前面用了“%”,所以该查询必然走全表查询,除非必要(模糊查询须要包含abc),不然不要在关键词前加%

正例

SELECT id FROM A WHERE name like 'abc%'

实例

mysql版本:5.7.26

select nick_name from member where nick_name like '%小明%'

like'%小明%'并未使用索引!

select nick_name from member where nick_name like '小明%'

like'小明%'成功使用索引!

6.where子句使用or的优化

一般使用 union all 或 union 的方式替换“or”会获得更好的效果。where子句中使用了or关键字,索引将被放弃使用。

反例

SELECT id FROM A WHERE num = 10 or num = 20

正例

SELECT id FROM A WHERE num = 10 union all SELECT id FROM A WHERE num=20

7.where子句中使用 IS NULL 或 IS NOT NULL 的优化

反例

SELECT id FROM A WHERE num IS NULL

在where子句中使用 IS NULL 或 IS NOT NULL 判断,索引将被放弃使用,会进行全表查询。

正例

优化成num上设置默认值0,确保表中num没有null值, IS NULL 的用法在实际业务场景下SQL使用率极高,咱们应注意避免全表扫描

SELECT id FROM A WHERE num=0

8.where子句中对字段进行表达式操做的优化

不要在where子句中的“=”左边进行函数、算数运算或其余表达式运算,不然系统将可能没法正确使用索引。

SELECT id FROM A WHERE datediff(day,createdate,'2019-11-30')=0

优化为

SELECT id FROM A WHERE createdate>='2019-11-30' and createdate<'2019-12-1'
SELECT id FROM A WHERE year(addate) <2020

优化为

SELECT id FROM A where addate<'2020-01-01'

9.排序的索引问题

mysql查询只是用一个索引,所以若是where子句中已经使用了索引的话,那么order by中的列是不会使用索引。所以数据库默认排序能够符合要求状况下不要使用排序操做;

尽可能不要包含多个列的排序,若是须要最好给这些列建立复合索引

10. 尽可能用 union all 替换 union

union和union all的差别主要是前者须要将两个(或者多个)结果集合并后再进行惟一性过滤操做,这就会涉及到排序,增长大量的cpu运算,加大资源消耗及延迟。因此当咱们能够确认不可能出现重复结果集或者不在意重复结果集的时候,尽可能使用union all而不是union

11.Inner join 和 left join、right join、子查询

  • 第一:inner join内链接也叫等值链接是,left/rightjoin是外链接。
SELECT A.id,A.name,B.id,B.name FROM A LEFT JOIN B ON A.id =B.id;

SELECT A.id,A.name,B.id,B.name FROM A RIGHT JOIN ON B A.id= B.id;

SELECT A.id,A.name,B.id,B.name FROM A INNER JOIN ON A.id =B.id;

通过来之多方面的证明 inner join性能比较快,由于inner join是等值链接,或许返回的行数比较少。可是咱们要记得有些语句隐形的用到了等值链接,如:

SELECT A.id,A.name,B.id,B.name FROM A,B WHERE A.id = B.id;

推荐:能用inner join链接尽可能使用inner join链接

  • 第二:子查询的性能又比外链接性能慢,尽可能用外链接来替换子查询。

反例

mysql是先对外表A执行全表查询,而后根据uuid逐次执行子查询,若是外层表是一个很大的表,咱们能够想象查询性能会表现比这个更加糟糕。

Select* from A where exists (select * from B where id>=3000 and A.uuid=B.uuid);

执行时间:2s左右

正例

Select* from A inner join B ON A.uuid=B.uuid where b.uuid>=3000; 这个语句执行测试不到一秒;

执行时间:1s不到

  • 第三:使用JOIN时候,应该用小的结果驱动大的结果

left join 左边表结果尽可能小,若是有条件应该放到左边先处理,right join同理反向。如:

反例

Select * from A left join B A.id=B.ref_id where A.id>10

正例

select * from (select * from A wehre id >10) T1 left join B on T1.id=B.ref_id;

12.exist & in 优化

SELECT * from A WHERE id in ( SELECT id from B )
SELECT * from A WHERE id EXISTS ( SELECT 1 from A.id= B.id )

分析:

in 是在内存中遍历比较

exist 须要查询数据库,因此当B的数据量比较大时,exists效率优于in**

in()只执行一次,把B表中的全部id字段缓存起来,以后检查A表的id是否与B表中的id相等,若是id相等则将A表的记录加入到结果集中,直到遍历完A表的全部记录。

In 操做的流程原理如同一下代码

List resultSet={};

    Array A=(select * from A);
    Array B=(select id from B);

    for(int i=0;i<A.length;i++) {
          for(int j=0;j<B.length;j++) {
          if(A[i].id==B[j].id) {
             resultSet.add(A[i]);
             break;
          }
       }
    }
    return resultSet;

能够看出,当B表数据较大时不适合使用in(),由于会把B表数据所有遍历一次

如:A表有10000条记录,B表有1000000条记录,那么最多有可能遍历10000*1000000次,效率不好。

再如:A表有10000条记录,B表有100条记录,那么最多有可能遍历10000*100次,遍历次数大大减小,效率大大提高。

结论:in()适合B表比A表数据小的状况

exist()会执行A.length()次,执行过程代码以下

List resultSet={};
Array A=(select * from A);
for(int i=0;i<A.length;i++) {
    if(exists(A[i].id) {  //执行select 1 from B where B.id=A.id是否有记录返回
       resultSet.add(A[i]);
    }
}return resultSet;

当B表比A表数据大时适合使用exists(),由于它没有那么多遍历操做,只须要再执行一次查询就行。

如:A表有10000条记录,B表有1000000条记录,那么exists()会执行10000次去判断A表中的id是否与B表中的id相等。

如:A表有10000条记录,B表有100000000条记录,那么exists()仍是执行10000次,由于它只执行A.length次,可见B表数据越多,越适合exists()发挥效果。

再如:A表有10000条记录,B表有100条记录,那么exists()仍是执行10000次,还不如使用in()遍历10000*100次,由于in()是在内存里遍历比较,而exists()须要查询数据库,

咱们都知道查询数据库所消耗的性能更高,而内存比较很快。

结论:exists()适合B表比A表数据大的状况
gif5新文件(1).gif

相关文章
相关标签/搜索