MySQL进阶学习笔记一(未完待续)

一、Linux安装MySQL

1、yum安装

1、下载设置安装源

 #下载安装源
wget http://repo.mysql.com/mysql57-community-release-el6-8.noarch.rpm
#安装rpm包
rpm -ivh mysql57-community-release-el6-8.noarch.rpm 
#修改安装源
vim /etc/yum.repos.d/mysql-community.repo
将[mysql55-community]的enabled设置为1,[mysql57-community]的enabled设置为0

2、安装mysql

yum -y install mysql-server mysql
-----解决yum锁问题------
rm -f /var/run/yum.pid

3、设置
数据库字符集设置

mysql配置文件  /etc/my.cnf中加入
character-set-server=utf8

4、启动/停止 服务

启动mysql服务:
service mysqld start
或者
/etc/init.d/mysqld start
停止服务:
service mysqld stop
开机启动:
chkconfig mysqld on,查看开机启动设置是否成功chkconfig --list | grep mysql*

5、登录

查询mysql第一次安装时的随机密码:
grep 'password' /var/log/mysqld.log |head -n 1
创建root管理员:  
mysqladmin -u root password 123456
登录:          
mysql -u root -p输入密码即可

6、远程访问

开放防火墙的3306端口号
/etc/sysconfig/iptabls 添加端口记录:
重启防火墙
service iptables restart
创建远程访问用户:
create user 'root'@'%' identified by 'root';

7、授权

 grant all privileges on *.* to 'root'@'%';

2、rpm安装

1、检查是否已经安装Mysql

#检查是否安装过mysql
rpm -qa | grep -i mysql
#卸载
rpm -e xxxxxx

2、下载Mysql相关RPM包

#下载相关rpm包
MySQL-client-5.6.40-1.el6.x86_64.rpm
MySQL-devel-5.6.40-1.el6.x86_64.rpm
MySQL-server-5.6.40-1.el6.x86_64.rpm

3、安装server->devel->client

#安装mysql服务
rpm -ivh MySQL-server-5.6.40-1.el6.x86_64.rpm
#安装mysql库信息
rpm -ivh MySQl-devel-5.6.40-1.el6.x86_64.rpm
#安装mysql客户端
rpm -ivh MySQl-client-5.6.40-1.el6.x86_64.rpm

4、将MySQL的配置文件拷贝到/etc目录下

#拷贝mysql配置文件
cp /usr/share/mysql/my-default.cnf /etc/my.cnf
#添加如下配置
port=3306
character-set-server=utf8

5、启动Mysql服务器

#启动服务器
service mysql start

6、查看root密码并且修改密码

#查看root账号随机密码
cat /root/.mysql_secret
#登录mysql服务器
mysql -uroot -p
#修改密码

7、配置远程访问

开放防火墙的3306端口号
/etc/sysconfig/iptabls 添加端口记录:
重启防火墙
service iptables restart
创建远程访问用户:
create user 'root'@'%' identified by 'root';
授权
grant all privileges on *.* to 'root'@'%';

3、MySQL安装相关路径设置

  1. /usr/bin #相关命令目录 #启动脚本
  2. /usr/share/mysql #配置文件目录
  3. /etc/my.cnf #数据库核心配置
  4. /var/lib/mysql/ #数据库目录

二、MySQL的主要配置文件

2.1、二进制日志文件log-bin

主从复制

2.2、错误日志log-error

默认关闭,用于记录严重的警告和错误信息,每次启动和关闭的详细信息等

2.3、查询日志log

默认关闭,记录所执行的sql语句的执行信息

2.4、数据文件

默认路径:/var/lib/mysql

  1. frm文件:存放表结构
  2. myd文件:存放表数据
  3. myi文件:存放索引信息

2.5、核心配置文件

/etc/my.cnf

三、MySQL的逻辑架构

MySQL进阶学习笔记一(未完待续)_第1张图片
1、最上层的服务并不是Mysql独有的,大多数基于网络的客户端/服务器的工具或者服务都有类似的架构;
2、第二层架构中,大多数的MYSQL的核心服务功能都在这一层,包括查询解析、分析、优化、缓存以及所有的内置函数(日期时间等),所有跨存储引擎的功能都在这一层实现:存储过程、触发器、视图等;
3、第三层包含了存储引擎。存储引擎负责Mysql中的数据的存储和提取。Mysql支持各种不同的存储引擎,每个存储引擎都有它的优势和劣势。服务器通过API和存储引擎进行通信。这些API接口屏蔽了不同存储引擎之间的差异,使的这些差异对上层的查询过程透明。存储引擎不会解析SQL(InnoDB是个例外,它会解析外键定义),不同的存储引擎之间也不能互相通信,而只是简单的响应上层服务器的请求。

四、MySQL的存储引擎

4.1、相关命令

查询当前数据库所支持的存储引擎

show engines;

查询当前默认的存储引擎

show variables like '%storage_engine%'

查看表的相关信息

show table status like '表名'

字段含义

  1. Name:表名
  2. Engine:表的存储引擎类型
  3. Rows:表中的行数,对于MyISAM,该值时精确的,对于InnoDB该值时估计值
  4. Avg_row_length:平均每行包含的字节数
  5. Data_length:表数据的大小
  6. Index_length:索引的大小
  7. Auto_increment:下一个AUTO_INCREMENT的值
  8. Create_time:创建的时间

4.2、MyISAM和InnoDB的比较

MySQL进阶学习笔记一(未完待续)_第2张图片

4.3、选择合适的存储引擎

如果应用需要不同的存储引擎,请考虑一下几个因素:

  1. 事务
    如果应用需要事务支持,那么InnoDB是目前最稳定并且经过验证的选择。如果不需要事务,并且主要是select和insert操作,则MyISAM是个不错的选择,一般日志型应用比较符合这一个特性。
  2. 备份
    备份的需求也会影响存储引擎的选择。如果需要在线热备份,则选择InnoDB就是基本的要求。
  3. 崩溃恢复
    数据量比较大的时候,系统崩溃后如何快速恢复是一个需要考虑的额问题。相对而言,MyISAM崩溃后发生损坏的概率比InnoDB高的多,而且恢复也慢,因此即使不需要事务,通常也应该选择InnoDB引擎。
  4. 特有的特性
    应该更多的考虑各个存储引擎自身的特性,综合的选择一个合适的搜索引擎。如果无法确定的时候,通常InnoDB是个不错的选择。

五、SQL性能下降的可能性

  1. 查询语句编写不合理;
  2. 索引失效;
  3. 关联查询太多;
  4. 服务器的参数设置问题;
  5. 等等。

注意:数据库的优化,往往是在设计数据库的时候就已经开始考虑后续的一个优化的问题。

六、数据类型的优化

6.1、选择最优的数据类型

Mysql支持的数据类型非常多,选择正确的数据类型对于获得高性能至关重要。

数据类型选择的原则

  1. 更小的通常更好:
    通常情况下,应该选择可以正确存储数据的最小数据类型。更小的数据类型通常更快,因为它们占用更小的磁盘、内存和CPU缓存。但是要确保没有低估需要存储值得范围,在MYSQL中的多个地方增加数据类型范围是一个非常耗时和痛苦的操作。

  2. 简单就好:
    简单数据类型的操作通常需要更少的CPU消耗。比如,整型比字符操作代价更低。举个例子:
    ①应该选择MySQL内建的类型(date、time等)存储时间,而不是字符串。
    ②应该用整型存储IP地址。

  3. 尽量避免NULL:
    通常情况下最好执行列为NOT NULL,除非真的需要存储NULL值。如果查询总包含可能为NULL的列,对于MySQL来说更难优化,因为可为NULL的列使的索引、索引统计等都变的更加复杂。通常把字段设置为NOT NULL带来的性能提升比较小,但是如果计划在列上创建索引,就应该尽量避免设计成可为NULL的列。

6.2、具体的数据类型

6.2.1、整数类型

  1. tinyint:8位
  2. smallint:16位
  3. mediumint:24位
  4. int:32位
  5. bigint:64位
  6. unsigned属性:
    添加unsigned属性表示该字段不允许负数,正数的上限大致可以提高一倍。比如tinyint unsigned可以存储0 ~ 255的范围。而tinyint是-128~127的范围。有符号和无符号占用空间大小相同,具有相同的性能。
    注意
    int(11)是指定整数类型的宽度,它不会限制值得合法范围,对于存储和计算而言,int(1)和int(20)没什么区别。

6.2.2、实数类型

实数是带小数部分的数字。

浮点类型

  1. float:32位
  2. double:64位

精准类型

  1. decimal:decimel需要额外的空间和计算开销,所以应该尽量只对小数进行精准计算时才使用decimel,例如存储金额等信息。

6.2.3、字符串类型

  1. varchar:
    varchar用于处处可变长的字符串,是最常见的字符串数据类型。它比定长更节省空间。varchar需要使用1~2个额外字节记录字符串的长度。varchar节省了存储空间,所以对性能也有帮助,但是由于长度可变,在update时可能使行变得比原来更长,这就导致需要进行额外的工作。至于如何进行空间增长取决于不同的存储引擎。当字符串列的最大长度比平均长度要大很多,并且列的更新很少时比较适合使用varchar。
  2. char:
    定长字符串,mysql总是根据定义的字符串长度分配足够的空间。char非常适合存储很短的字符串,或者值得长度都很接近的字段。例如char非常适合存放密码的md5值,因为这是一个定长的值。对于经常变更的字段,使用char也更为合适,因为定长的char类型不容易产生碎片。对于非常短的列,存储空间也更有优势,比如char(1)只会占用一个字节,而varchar(1)会用到两个字节,因为还有一个字节用来记录varchar的长度。
  3. blob和text:
    两者都是用于存储很大的数据而设计的字符串数据类型,分别采用二进制和字符的方式存储。

注意:varchar(5)和varchar(200)存储’hello’的空间开销是一样的。但是varchar(5)对性能提升有很大的优势。更长的列会消耗更多的内存,因为mysql通常会分配固定大小的内存块来保存内部值。尤其是使用内存临时表进行排序等操作时会特别糟糕。所以最好的策略是只分配真正需要的空间。

6.2.4、日期和时间类型

  1. datetime:
    能保存大范围的值,从1001年到9999年,精度为秒。它把日期和时间封装到YYYYMMDDHHMMSS的整数中,使用8个字节的存储空间。
  2. timestamp:
    保存了从1970年1月1日以来的毫秒数,timestamp只使用了4个字节的存储空间,因此它的范围比datetime小的多;但是只能表示从1970年到2038年。另外timestamp也依赖于时区。

注意:除了特殊行为之外,通常应该尽量使用timestamp,因为它比datetime空间效率更高。
MySQL进阶学习笔记一(未完待续)_第3张图片

七、索引优化

7.1、索引基础知识

7.1.1、什么是索引?

索引是存储引擎用于快速找到记录的一种数据结构。索引对于良好的数据库查询性能而言是非常关键的,尤其当表中的数据量越来越大时,索引对性能的影响越发重要。索引可以说是对查询性能优化最有效的手段了。因为索引能够轻易将查询性能提高几个数量级,将一个可能需要几百秒的查询语句提升到只需要几秒。但是索引经常被忽略,而不恰当的索引对性能可能还会带来负面效果,所以在使用索引的时候,需要考虑需求,不要一味的追求索引。

7.1.2、那么什么时候该加索引?

  1. 主键自动建立主键索引(唯一索引);
  2. where字句中的列,频繁作为查询字段的列;
  3. 表连接关联的列;
  4. 排序用到的列;
  5. 索引的基数越大(选择性大),索引的效率就越高,什么叫基数越大,比如手机号,每个列都具有不同的值,非常好区别,这个就适合建立索引,而性别这样的字段,因为只有两个值,以不适合建立索引,就是区分度高低的问题。
  6. 等等

7.1.3、那么什么时候不该加索引?

  1. 表记录太少;
  2. 频繁修改的字段;
  3. 数据重复且分布平均的字段。
  4. 等等

7.1.4、索引分类

1、单值索引
一个索引只包含单个列,一个表可以有多个单列索引。
2、复合索引
一个索引包含多个列。
3、唯一索引
索引列的值必须唯一,但是允许有空值。
4、全文索引
全文索引,通过建立倒排索引,可以极大的提升检索效率,解决判断字段是否包含的问题.。例如: 有title字段,需要查询所有包含 "政府"的记录. 需要 like "%政府%“方式查询,查询速度慢,当查询包含"政府” OR "中国"的需要是,sql难以简单满足。全文索引就可以实现这个功能。
详细的自行百度。

5、索引的基本语法

  1. 创建索引
create [unique|fulltext] index 索引名 on 表名 (属性名[长度][asc|desc]);
  1. 删除索引
drop index 索引名 on 表名;
  1. 查看索引
show index from 表名;

7.1.5、B-tree索引详解

什么是B-tree索引?
通常我们所说的索引是指B-Tree索引,它是目前关系型数据库中查找数据最为常用和有效的索引,大多数存储引擎都支持这种索引。使用B-Tree这个术语,是因为MySQL在CREATE TABLE或其它语句中使用了这个关键字,但实际上不同的存储引擎可能使用不同的数据结构,比如InnoDB就是使用的B+Tree。

什么是哈希表?
自行百度

什么是二叉搜索树
自行百度

为什么采用B-tree设计索引而不用哈希表或者二叉搜索树?
随着数据库中数据的增加,索引本身大小随之增加,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。这样的话,索引查找过程中就要产生磁盘I/O消耗,相对于内存存取,I/O存取的消耗要高几个数量级。可以想象一下一棵几百万节点的二叉树的深度是多少?如果将这么大深度的一颗二叉树放磁盘上,每读取一个节点,需要一次磁盘的I/O读取,整个查找的耗时显然是不能够接受的。那么如何减少查找过程中的I/O存取次数?
一种行之有效的解决方法是减少树的深度,将二叉树变为m叉树(多路搜索树),而B+Tree就是一种多路搜索树。理解B+Tree时,只需要理解其最重要的两个特征即可:第一,所有的关键字(可以理解为数据)都存储在叶子节点(Leaf Page),非叶子节点(Index Page)并不存储真正的数据,所有记录节点都是按键值大小顺序存放在同一层叶子节点上。其次,所有的叶子节点由指针连接。
注意:B-tree的非叶子节点也存放数据,B+树是B-tree的变种。

7.2、explain-执行计划

7.2.1、什么是执行计划?

执行计划可以模拟Mysql优化器执行SQL查询语句,从而了解Mysql是如何处理被执行的SQL语句的。可以帮助程序员分析和了解这条SQL语句的性能瓶颈。

语法:

explain select语句

查询字段含义(这里只列举主要的字段):

7.2.1.1、id(重要)

作用:id主要是用来标识sql执行顺序。
id相同:如果没有子查询时会出现,执行顺序也是从上到下。

#查询所有学生
explain select * from student;
#关联查询所有学生和班级信息
explain select * from student s inner join class c on s.cid = c.id;
id不同:如果是子查询,则每个查询会有不同的id,并且依次递增。id值越大,表示优先级越高,越先被执行
#查询id为1的班级中的所有学生信息
explain select * from student where cid = (select id from class where id = 1)
id相同不同,同时存在:先查询id值大的,id相同,从上到下顺序执行
#
explain select * from (select * from student) s inner join class c on c.id = s.id
id为null的情况,如果id为null表示该部分最后执行

7.2.1.2、select_type

作用:每个select子句的类型。
mysql对查询的分类:

  • 简单的sql语句:没有任何子查询;
  • 复杂的sql语句:1、在select和where后面存在子查询;2、在from后面存在子查询;3、含有union的sql语句。
    SIMPLE:查询中不包含任何子查询或者union
explain select * from student;

PRIMARY:查询中包含了任何复杂的子部分,最外层的就会变成PRIMARY (最后被执行的查询)

#
explain select * from student where cid = (select id from class where id = 1);
#
explain select * from (select * from student) s inner join class c on c.id = s.id

SUBQUERY:在SELECT或者WHERE列表中的子查询标记为 SUBQUERY

explain select * from student where cid = (select id from class where id = 1);

DERIVED:在FROM中包含的子查询被标记为 DERIVED(衍生表)

explain select * from (select * from student) s

UNION:如果第二个SELECT出现在UNION之后,则被标记为UNION,如果UNION包含在FROM子句的子查询中,第一个SELECT会被标记为:DERIVED

#
explain 
select * from student where score <= 1 
union 
select * from student where score >= 1.5
#
explain 
select * from
(select * from student where score <= 1 
union 
select * from student where score >= 1.5) s

UNION RESULT:从UNION表获取结果的select

explain 
select * from student where score <= 1 
union 
select * from student where score >= 1.5

7.2.1.3、type(重要)

作用:表示访问类型,换而言之就是Mysql查找表中行的方式,下面的访问方式,从最差到最优。
null > system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
all:表示全表扫描,效率最低

#
explain select * from student where name = '小明';
#
explain select * from student where age > 5;

index:这个跟全表扫描一样,只是MySQL扫描表时,按索引次序进行而不是行。它的主要作用是避免了排序;缺点是要承担按索引次序读取整个表的开销;如果在extra中看到’Using index’,说明MySQL正在使用覆盖索引,它只扫描索引的数据,而不是按索引次序的每一行,这样效率就会高效很多。

#
explain select id from student
#
explain select * from student order by id

range:范围扫描就是一个有限制的索引扫描,它开始于索引里的某一点,返回匹配这个值域的行

#
explain select * from student where id > 2

ref:这是一种索引访问,它返回所有匹配某个值得行。然后它可能会找到多个符合条件的行,因此它是查找和扫描的混合体。此类访问只有使用非唯一索引或者唯一索引的非唯一性前缀时才会发生。

#创建索引
create index index_age on student(age);
#
explain select * from student where age = 5

eq_ref:使用这种搜索查找,Mysql知道最多只返回一条符合条件的记录。这种访问方法可以在Mysql使用主键或者唯一性索引联合查询时看到

explain select * from student s inner join class c on s.cid = c.id

const:当Mysql能对查询的某部分进行优化并将其转换成一个常量时,它就会使用这些访问类型。举例来说,当你通过某一行的主键放入where字句里的方式来查询时,MySQL会把这部分操作转换为一个常量。就是直接取数据。

explain select * from student where  id = 1

system:表只有一行记录,这是const类型的特例,通常不会出现。

explain select * from (select * from student where id = 1) s

NULL:这种方式意味着MySQL能在优化阶段分解查询语句,在执行阶段甚至用不着再访问表或索引。这是最优的效果。

#
explain select 1+2;
#
explain select * from student where id = 1 and id = 2

**注意:**通常在生产环境中,得保证查询至少达到range级别,最好能达到ref级别以上。

7.2.1.4、possible_keys

可能应用到这个查询上的索引。

7.2.1.5、key(重要)

实际使用的索引,如果为NULL,则没有使用任何索引。如果该索引没有出现在possible_keys列中,那么Mysql选用它就是出于另外的原因。换句话说,possible_keys表示哪个索引能有助于高效的行查找,而key显示的是优化采用哪个索引可以最小化查询成本。

7.2.1.6、key_len

使用索引的长度,在不损失精准性的情况下,长度越短越好。

7.2.1.7、ref

表示查询记录时,所用到列或常量。

7.2.1.8、rows (重要)

这一列是MySQL估计为了查找所需的记录而需要读取的行数,他不一定等于最终从表里读取出来的行数。

7.2.1.9、Extra (重要)

关于MYSQL如何解析查询的额外信息。

  1. Using index : 效率高,表示MySQL使用了覆盖索引,避免了表的访问。(very good);
  2. Using temporary:意味着MySQL对查询结果排序时,会使用一个临时表(尽量杜绝);
  3. Using filesort:这意味着MySQL会对结果使用一个外部索引排序,而不是按索引次序从表里读取行。这个排序有可能在内存中,也有可能在磁盘上。(尽量杜绝);
  4. Using where:意味着MySQL将在存储引擎检索行后再进行过滤。

7.3、高性能的索引策略

正确的创建和使用索引是实现高性能查询的基础。

7.3.1、多列索引

为什么选择多列索引,而不是每个列单独创建索引?
在多数情况下,在多个列上建立独立的索引并不能提高查询性能。理由非常简单,MySQL不知道选择哪个索引的查询效率更好,所以在老版本,比如MySQL5.0之前就会随便选择一个列的索引,而新的版本会采用合并索引的策略。

** 选择合适的索引顺序:** 应该将选择性更高的列放在前面。
** 注意:** 当出现多个索引做相交操作时(多个AND条件),通常来说一个包含所有相关列的索引要优于多个独立索引。当出现多个索引做联合操作时(多个OR条件),对结果集的合并、排序等操作需要耗费大量的CPU和内存资源,特别是当其中的某些索引的选择性不高,需要返回合并大量数据时,查询成本更高。所以这种情况下还不如走全表扫描。

7.3.2、独立的列

索引列不能是表达式的一部分,也不能是函数的参数。应该养成简化where条件的习惯,始终将索引列单独放在运算符的一侧。

select * from  ... where id + 1 = 5;

这是一个错误的用法,mysql无法解析id + 1 = 5 这个方程式,故不会使用到id列上的索引。

7.3.3、前缀索引

什么是前缀索引?
有时候需要索引很长的字符列,这会让索引变得很大且慢。通常可以索引开始的部分字符,这样可以大大节约索引空间,从而提高索引效率。但是这样也会降低索引的选择性。
创建前缀索引语法

CREATE INDEX 索引名 ON 表名 (字段名(前缀长度))

索引选择性
什么是索引选择性?
索引的选择性是指,不重复的索引值和数据表的总记录的比值。索引选择性越高则查询效率越高,索引选择性可以让mysql在查询时过滤掉更多的行。
计算完整列的选择性:

select count(distinct 列名)/count(*) from 表名

选择合适的前缀索引长度
前缀索引的长度选择应该在一个合适的范围,不能太长同时要保证较高的选择性。前缀索引的选择性应该接近于完整列的选择性。
计算前缀索引的选择性

select count(distinct left(列名, 长度))/count(1) from 表名

注意:前缀索引也有缺点,mysql无法使用前缀索引做order by和group by

7.3.4、全列匹配

和索引中的所有列进行匹配。

7.3.5、最左前缀匹配

和索引中的第一列进行匹配。

7.3.6、匹配列前缀

和索引中的第一列的开头部分进行匹配。比如:like ‘a%’

7.3.7、匹配范围值

和索引中的第一列的范围匹配。

7.3.8、精确匹配某一列并范围匹配另外一列

第一列全值匹配,第二列范围匹配。

7.3.9、聚簇索引

什么是聚簇索引?
聚簇索引实际上就是在同一个结构中保存了B-tree索引和数据行。
创建聚簇索引
MySQL无法主动创建聚簇索引,InnoDB是将我们的主键作为聚簇索引。如果没有定义主键,则InnoDB会选择一个唯一的非空索引代替。如果没有这样的索引,则InnoDB会隐式的定义一个主键来作为聚簇索引。
二级索引
二级索引即普通索引,和聚簇索引不同。二级索引的叶子节点中存储的不是行指针,而是主键值。即二级索引的叶节点都指向聚簇索引对应的主键。

**注意:**在使用InnoDB表时,应该提供一个自动增长的列作为主键,这种主键和业务应该无关,这样可以保证数据行是按顺序写入,对于根据主键做关联操作的性能也会更好。

7.3.10、覆盖索引

如果一个索引包含或者说覆盖所有需要查询的字段的值,那么就没有必要再回表查询,这就称为覆盖索引。
**优势:**索引条目远小于数据行大小,如果只读取索引,极大减少数据访问量;索引是有按照列值顺序存储的,对于I/O密集型的范围查询要比随机从磁盘读取每一行数据的IO要少的多。

7.3.11、索引的限制(索引失效)

  1. 如果以通配符开头的条件(’%aaa’),mysql索引会失效;
  2. or会导致索引失效;
  3. 如果使用 is null, is not null 不能使用索引;
  4. 如果在索引列上使用了!=、<>时会使索引失效;
  5. 字符串不加单引号为导致索引失效;
  6. 如果在索引列上做任何操作(计算、函数、(自动或者手动)类型转换),会导致索引失效进而变成全表扫描;
  7. 查询中某个列有范围查询,则其右边的所有列都无法使用索引优化查询。;
  8. 不能跳过索引中的列,比如查询第一列和第三列,而不指定第二列的话,则只能使用索引的第一列;
  9. 如果不按照最左前缀原则,则无法使用索引;
  10. 等等。

7.4、索引分析案例一

后续补充。。。。。

7.5、索引自测

当前有索引index(a,b,c),问以下的查询是否能用到索引,如果用上了,则哪几个字段会用上索引?

  1. where a=3
  2. where a=3 and b = 5
  3. where a=3 and b=5 and c=4
  4. where b = 3
  5. where b = 3 and c = 4
  6. where a = 3 and c = 5
  7. where a/10 = 20
  8. where a = 3 and b > 4 and c = 5
  9. where a = 3 and b like ‘ss%’ and c = 4
  10. where a = 3 and b like ‘%ss’ and c = 4
  11. where a = 3 and b like ‘%ss%’
  12. where a = 3 and b like ‘ss%ss%’

八、查询优化

8.1、SQL语句的执行顺序

sql语句的编写顺序:

select 列名1,列名2...
from 表名1
[inner join 表名2 on 表名1.公共列=表名2.公共列]
[left join 表名2 on 表名1.公共列=表名2.公共列]
[right join 表名2 on 表名1.公共列=表名2.公共列]
[where 条件//边查询边筛选]
[group by 列名 //查询完后再将结果进行分组]
[having 条件 //查询完分完组后再筛选 ]
[order by 列名[asc/desc] //对结果排序]
[limit start [,count]]//只有mysql可用;

sql语句的执行顺序
MySQL进阶学习笔记一(未完待续)_第4张图片

分析
从这个顺序中我们不难发现,所有的 查询语句都是从from开始执行的,在执行过程中,每个步骤都会为下一个步骤生成一个虚拟表,这个虚拟表将作为下一个执行步骤的输入。

  1. 首先对from子句中的前两个表执行一个笛卡尔乘积,此时生成虚拟表 vt1(选择相对小的表做基础表);
  2. 接下来便是应用on筛选器,on 中的逻辑表达式将应用到 vt1 中的各个行,筛选出满足on逻辑表达式的行,生成虚拟表 vt2 ;
  3. 如果是left outer jion 就把左表在第二步中过滤的添加进来,如果是right outer join 那么就将右表在第二步中过滤掉的行添加进来,这样生成虚拟表 vt3 ;
  4. 如果 from 子句中的表数目多余两个表,那么就将vt3和第三个表连接从而计算笛卡尔乘积,生成虚拟表,该过程就是一个重复1-3的步骤,最终得到一个新的虚拟表 vt3;
  5. 应用where筛选器,对上一步生产的虚拟表引用where筛选器,生成虚拟表vt4;
  6. group by 子句将中的唯一的值组合成为一组,得到虚拟表vt5;
    注意:如果应用了group by,那么后面的所有步骤都只能得到的vt5的列或者是聚合函数(count、sum、avg等)
  7. 应用having筛选器,生成vt6,having筛选器是第一个也是为唯一一个应用到已分组数据的筛选器;
  8. 处理select子句,将vt6中的在select中出现的列筛选出来,生成vt7;
  9. 应用distinct子句,vt7中移除相同的行,生成vt8,事实上如果应用了group by子句那么distinct是多余的;
  10. 应用order by子句,按照order_by_condition排序vt8,此时返回的一个游标,而不是虚拟表;
  11. 应用limit子句,对结果集分页,生成虚拟表vt9;
  12. 应用top选项,返回结果。

8.2、Mysql查询的过程

MySQL进阶学习笔记一(未完待续)_第5张图片

  1. 客户端发送一条查询给服务器;
  2. 服务器先检查查询缓存,如果命中缓存,则立即返回存储在缓存中的结果,否则进入下一阶段;
  3. 服务器端进行SQL解析、预处理,再由优化器生成对应的执行计划;
  4. MySQL根据优化器生成的执行计划,调用存储引擎的API来执行查询;
  5. 将结果返回给客户端,同时缓存查询结果;

8.3、慢查询日志

什么是慢查询日志?
MySQL的慢查询日志是MySQL提供的一种日志记录,它用来记录在MySQL中响应时间超过阈值的语句,具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中。long_query_time的默认值为10,意思是运行10S以上的语句。默认情况下,Mysql数据库并不启动慢查询日志,需要我们手动来设置这个参数,当然,如果不是调优需要的话,一般不建议启动该参数,因为开启慢查询日志会或多或少带来一定的性能影响。慢查询日志支持将日志记录写入文件,也支持将日志记录写入数据库表。

开启慢查询日志

查看慢查询日志:
select @@slow_query_log; 

开启:
SET GLOBAL slow_query_log=1;

查询慢查询次数

show status like 'slow_queries';

设置慢查询的阈值

查看阈值:
show variables like 'long%'
设置阈值:
set long_query_time=0.001;

查看慢查询日志的位置

select @@slow_query_log_file;

注意:每次删除慢查询日志,需要重新开启慢查询,日志才会自动生成

8.4、Show Profile

什么是Show Profile?
mysql提供的,可以用来分析当前会话中语句执行的资源消耗情况。

查询当前Mysql版本是否支持Show Profile

show variables like 'profiling';

开启Show Profiling

set profiling = on;

查看Show Profiling记录

show profiles;

诊断SQL

show profile cpu,block io for query 'N'
其中N是记录下来的sql语句id

需要优化的步骤

  • converting HEAP to MyISAM 查询结果太大,内存不够,写入磁盘中;
  • create tmp table 创建了临时表;
  • copying to tmp table on disk 把内存中临时表复制到磁盘;

8.5、特定类型的查询优化

表结构

#学生表
create table student(
id int primary key auto_increment,
name varchar(20) not null,
age int,
score float,
birthday timestamp,
cid int
);
#课程表
create table course(
id int primary key auto_increment,
cname varchar(10)
);
#成绩表
create table score(
sid int,
cid int,
score int default 0
);
insert into course 
	values
(null, "高等数学"),
(null, "线性代数"),
(null, "毛泽东思想"),
(null, "邓小平理论"),
(null, "马克思主义"),
(null, "计算机电路基础"),
(null, "操作系统"),
(null, "Mysql数据库"),
(null, "Oracle数据库"),
(null, "Java编程"),
(null, "C语言基础"),
(null, "二进制"),
(null, "概率学"),
(null, "大学英语"),
(null, "专业英语"),
(null, "PHP编程"),
(null, "C++"),
(null, "Pythod编程"),
(null, "云计算"),
(null, "大数据");
#批量插入学生记录
drop procedure if exists insert_stu;
delimiter &&
create procedure insert_stu()
begin
   declare i int default 0;
   A:loop
	insert into student value(null, concat("小明",i), i, rand(), now(), rand()*20);
	set i = i + 1;
	if i >= 200000 then
	    leave A;
	end if;
   end loop;
end &&
delimiter ;
call insert_stu();
#批量插入学生成绩
drop procedure if exists insert_score;
delimiter &&
create procedure insert_score()
begin
   declare i int default 1;
   declare j int default 1;
   A:loop
	set j = rand() * 20 + 1;
	B:loop
		insert into score value(i, j, rand() * 100);
		set j = j + rand() * 5 + 1;
		if j > 20 then
			leave B;
		end if;
	end loop B;
	set i = i + 1;
	if i > 200000 then 
		leave A;
	end if;
   end loop A;
end &&
delimiter ;
call insert_score();
#课程表20条
select count(*) from course;
#成绩表 70W+条
select count(*) from score;
#学生表 20W条
select count(*) from student;

8.5.1、查询成绩表的总条数

优化前

select count(1) from score

优化后

select count(*) from score

注意: COUNT()可能是被大家误解最多的函数了,它有两种不同的作用,其一是统计某个列值的数量,其二是统计行数。统计列值时,要求列值是非空的,它不会统计NULL。如果确认括号中的表达式不可能为空时,实际上就是在统计行数。最简单的就是当使用COUNT(* ) 时,并不是我们所想象的那样扩展成所有的列,实际上,它会忽略所有的列而直接统计所有的行数。
我们最常见的误解也就在这儿,在括号内指定了一列却希望统计结果是行数,而且还常常误以为前者的性能会更好。但实际并非这样,如果要统计行数,
直接使用COUNT(*),意义清晰,且性能更好。

8.5.2、查询学生姓名中包含’生’字的学生信息

优化前

#创建索引
create index idx_name on student(name)
#
select * from student where student.name like '%生%'
#根据执行计划可知是全表扫描

优化后

#通过覆盖索引提高效率
select name from student where student.name like '%生%'

8.5.3、查询所有学生按年龄从小到大排列

优化前

select * from student order by age

优化后

#创建索引
create index idx_age on student(age, name);
#
select name,age from student order by age

8.5.4、查询每门课选课的学生数

8.5.5、查找课程1考100分的考生

优化前

#未优化 - 子查询
select * from student where id in (select sid from score where cid = (select id from course where cname = '课程1') and score = 100);
#耗时  628.787771秒左右

优化后

#未添加索引 - 改成关联查询
select * from student st inner join score sc on st.id = sc.sid inner join course c on sc.cid = c.id where c.cname = '课程1' and sc.score = 100;
#耗时  0.167155秒左右
#添加索引
create index idx_cname on course(cname);
create index idx_cid_sco on score(cid, sid, score);
#添加索引后 - 子查询
select * from student where id in (select sid from score where cid = (select id from course where cname = '课程1') and score = 100);
#耗时  0.500977秒左右
#添加索引后 - 关联查询
select * from student st inner join score sc on st.id = sc.sid inner join course c on sc.cid = c.id where c.cname = '课程1' and sc.score = 100;
#耗时  0.042039秒左右

8.5.6、分页查询课程第10W页的记录

分页查询时可能存在的问题
分页的问题在于,当偏移量很大时,例如limit 100000,10 ;mysql需要查询100010条记录,但是只返回最后10条,前面的100000条记录都将被抛弃,代价很高。

解决方案
对于偏移量很大的时候,我们可以采用覆盖索引+延迟关联来解决这个问题。
延迟关联:通过使用覆盖索引查询返回需要的主键,再根据主键关联原表获得需要的数据。
例如:
优化前:

select film_id,description from film order by title LIMIT 100,10;

优化后:

select f.film_id,f.description 
from film f 
INNER JOIN 
(select film_id from film order by title limit 100,10) b 
on f.film_id=b.film_id;

九、库表结构优化

9.1、分区表

什么是分区表?
表分区,是指根据一定规则,将数据库中的一张表分解成多个更小的,容易管理的部分。从逻辑上看,只有一张表,但是底层却是由多个物理分区组成。
分区表适用的场景

  1. 某张表的数据量非常大,通过索引已经不能很好的解决查询性能的问题;
  2. 表的数据可以按照某种条件进行分类,以致于在查询的时候性能得到很大的提升。

查询Mysql是否支持分区

show variables like '%partition%';

分区表的类型

  1. 范围分区
    利用取值范围进行分区,区间要连续并且不能互相重叠。
    语法
partition by range(exp)( //exp可以为列名或者表达式,比如to_date(created_date)
    partition p0 values less than(num)
)

案例

create table emp(
id INT NOT null,
store_id int not null
)
partition by range(store_id)(
partition p0 values less than(10),
partition p1 values less than(20)
);

上面的语句创建了emp表,并根据store_id字段进行分区,小于10的值存在分区p0中,大于等于10,小于20的值存在分区p1中。 注意每个分区都是按顺序定义的,从最低到最高。

范围分区的问题
range范围覆盖问题:当插入的记录中对应的分区键的值不在分区定义的范围中的时候,插入语句会失败。
解决方案
设置分区的时候,使用values less than maxvalue 子句,MAXVALUE表示最大的可能的整数值。

  1. 列表分区
    列表分区是建立离散的值列表告诉数据库特定的值属于哪个分区。
    语法
partition by list(exp)( //exp为列名或者表达式
        partition p0 values in (3,5)  //值为3和5的在p0分区
    )

注意:如果插入的记录对应的分区键的值不在list分区指定的值中,将会插入失败。并且,list不能像range分区那样提供maxvalue。

  1. 哈希分区
    Hash分区主要用来分散数据,确保数据在预先确定个数的分区中尽可能平均分布。
    语法
partition by hash(store_id) partitions 4;
#根据store_id对4取模,决定记录存储位置。 比如store_id = 234的记录,MOD(234,4)=2,所以会被存储在第二个分区

注意:常规Hash分区的优点和不足 。
优点:能够使数据尽可能的均匀分布。
缺点:不适合分区经常变动的需求。

  1. 键值分区
    类似Hash分区,Hash分区允许使用用户自定义的表达式,但Key分区不允许使用用户自定义的表达式。Hash仅支持整数分区,而Key分区支持除了Blob和text的其他类型的列作为分区键。
    语法
partition by key(exp) partitions 4;//exp是零个或多个字段名的列表

查询优化
在where条件中带入分区列,有时候即时看上去多余也要带上。

执行计划查看分区

EXPLAIN  PARTITIONS

9.2、MyCat数据库中间件

MyCat介绍
一个彻底开源的,面向企业应用开发的大数据库集群;它支持事务、ACID、可以替代MySQL的加强版数据库;是一个可以视为MySQL集群的企业级数据库,用来替代昂贵的Oracle集群;是一个融合内存缓存技术、NoSQL技术、HDFS大数据的新型SQL Server;结合传统数据库和新型分布式数据仓库的新一代企业级数据库产品;一个新颖的数据库中间件产品。

MyCat的运用场景
1、读写分离
2、分库分表
3、多数据源处理

MyCat安装(centos)

  1. 将Mycat上传到指定路径/usr/local/software;
  2. 解压Mycat安装包
tar -zxf Mycat-server-1.6.7.4-release-20200105164103-linux.tar.gz
  1. 配置环境变量
#配置环境变量
vim /etc/profile
#添加内容
MYCAT_HOME=/usr/local/mysql/mycat
PATH=$PATH:$MYCAT_HOME/bin
export MYCAT_HOME PATH
#生效环境变量
source /etc/profile
  1. 开放端口8066和9066
    MyCat的默认数据端口为8066,mycat通过这个端口接收数据库客户端的访问请求。管理端口为9066,用来接收mycat监控命令、查询mycat运行状况、重新加载配置文件等。

  2. Mycat启动命令

#启动mycat
mycat start 

#按控制台的方式启动mycat
mycat console

#停止mycat
mycat stop

MyCat核心配置文件
server.xml:用户管理及相关系统配置。
schema.xml:逻辑库、表等相关配置。
rule.xml:分片规则相关配置。

MyCat处理读写分离
MySQL主从复制

  1. 主从复制的过程
    MySQL进阶学习笔记一(未完待续)_第6张图片
    1)在主库上把数据更改记录到二进制日志(Binary Log)中(这些记录被称为二进制日志事件)。在每次准备提交事务完成数据更新前会进行这一步的操作。
    2)从库将主库的日志复制到自己的中继日志中(Relay Log)。首先从库会启动一个工作线程(I/O线程),跟主库建立一个连接,然后在主库上启动一个特殊的二进制转储线程,这个二进制转储线程会读取主库上二进制日志中的事件。从库的I/O线程会将接收到的事件记录到中继日志中。
    3)从库的SQL线程会读取中继日志中的事件,并在从库上执行,从而实现从库的数据同步。

  2. 配置主从复制
    步骤
    1)、在每台服务器上创建复制账号;
    2)、配置主库和从库;
    3)、通知从库连接到主库并从主库复制数据;
    创建复制账号

GRANT REPLICATION SLAVE, REPLICATION CLIENT
ON *.*
TO 'repl'@'%' 
IDENTIFIED BY 'repl'

配置主库
my.cnf文件中:

[mysqld]
server_id=1
log_bin=mysql-binlog
binlog_do_db=mydb   

log_bin表示二进制文件的名称;server_id表示服务器的标识,唯一就行了;binlog_do_db表示需要复制的主数据库名字。
查看主库的二进制文件状态

show master status;

配置从库

log_bin = mysql-bin
server_id = 2
relay_log = /var/lib/mysql/mysql-relay-bin
log_slave_updates = 1

relay_log表示配置中继日志的位置与文件名;log_slave_updates表示允许从库将重放的事件也记录到自身的二进制日志中。

配置从库连接主库

CHANGE MASTER TO MASTER_HOST='192.168.101.148',
MASTER_USER='repl',
MASTER_PASSWORD='repl',
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=0;

查看slave状态

show slave status;

开启复制

start slave
  1. 使用docker的方式配置主从复制

MyCat搭建读写分离服务
MyCat配置

  1. 配置MyCat核心配置文件


<mycat:schema xmlns:mycat="http://io.mycat/">
	 
      <schema name="rc_schema2" checkSQLschema="false" sqlMaxLimit="100" dataNode="rc_dn2">schema>
 
      
   
      <dataNode name="rc_dn2" dataHost="dtHost2" database="mydb" />
 
      
      
      
      <dataHost name="dtHost1" maxCon="500" minCon="20" balance="1"
           writeType="0" dbType="mysql" dbDriver="native" switchType="2" slaveThreshold="100">
           <heartbeat>show slave statusheartbeat>
           
           <writeHost host="master" url="192.168.101.130:3306" user="root" password="root" >
				<readHost host="slave" url="192.168.101.131:3306" user="root" password="root" />
		   writeHost>
      dataHost>
      
	  
      
      
      
      
      
mycat:schema>
  1. 配置MyCat代理文件



<mycat:server xmlns:mycat="http://io.mycat/">
	<system>
	<property name="useSqlStat">0property>  
	<property name="useGlobleTableCheck">0property>  
		<property name="sequnceHandlerType">2property>
       
         
	
	
		
		<property name="processorBufferPoolType">0property>
		
		
		
		
		
		
		
		
		<property name="handleDistributedTransactions">0property>
		
			
		<property name="useOffHeapForMerge">1property>
		
		<property name="memoryPageSize">1mproperty>
		
		<property name="spillsFileBufferSize">1kproperty>
		<property name="useStreamOutput">0property>
		
		<property name="systemReserveMemorySize">384mproperty>
		
		<property name="useZKSwitch">trueproperty>
	system>
	
	
	
	
	<user name="root">
		<property name="password">rootproperty>
		<property name="schemas">rc_schema2property>
	user>
	
mycat:server>
  1. 配置主机名

MyCat处理分库分表

9.3、分表

什么是分表?

水平拆分
水平分表就是根据一列或多列数据的值把数据行放到多个独立的表里,这里不具备业务意义。用于解决解决表行数过大问题。

垂直拆分
垂直分表就是把一个数据量很大的表,可以按某个字段的属性或使用频繁程度分类,拆分为多个表。解决列过长问题。

9.4、分库

什么是分库?

十、硬件层面优化

  1. 更大的内存,减少磁盘IO;
  2. 采用多核CPU,可以提高MySQL的执行速度;
  3. 采用固态硬盘,随机IO性能更佳。

你可能感兴趣的:(mysql学习笔记,数据库,mysql,数据库)