一、 MySQL 简介
1.1什么是 Mysql
- MySQL 是一个关系型数据库管理系统, 由瑞典 MySQL AB 公司开发, 目前属于 Oracle 公司。
- Mysql 是开源的, 可以定制的, 采用了 GPL 协议, 你可以修改源码来开发自己的 Mysql 系统。
- MySQL 使用标准的 SQL 数据语言形式。
- Mysql 可以允许于多个系统上, 并且支持多种语言。 这些编程语言包括 C、 C++、 Python、 Java、 Perl、 PHP、Eiffel、 Ruby 和 Tcl 等。
- MySQL 支持大型数据库, 支持 5000 万条记录的数据仓库, 32 位系统表文件最大可支持 4GB, 64 位系统支持最大的表文件为 8TB。
1.2Mysql 高手是怎样练成的
- 数据库内部结构和原理
- 数据库建模优化
- 数据库索引建立
- SQL 语句优化
- SQL 编程(自定义函数、 存储过程、 触发器、 定时任务)
- mysql 服务器的安装配置
- 数据库的性能监控分析与系统优化
- 各种参数常量设定
- 主从复制
- 分布式架构搭建、 垂直切割和水平切割
- 数据迁移
- 容灾备份和恢复
- shell 或 python 等脚本语言开发
- 对开源数据库进行二次开发
1.3在 Linux 上安装 Mysql
1.3.1 准备工作
1.3.1.1 检查当前系统是否安装过 Mysql
(1) CentOS7 环境下
默认 Linux(CentOS7) 在安装的时候, 自带了 mariadb(mysql 完全开源版本)相关的组件。
先卸载系统自带的 mariadb, 执行卸载命令 rpm -e –nodeps mariadb-libs
1.3.1.2 检查/tmp 文件夹权限
查看/tmp 文件夹权限:
赋予其最大权限:
1.3.2 Mysql 的安装
安装的版本是 mysql 5.5, 官网下载地址: http://dev.mysql.com/downloads/mysql/
①将 rpm 安装包拷贝到 opt 目录下
②在安装目录下执行 rpm 安装
rpm -ivh MySQL-client-5.5.54-1.linux2.6.x86_64.rpm
rpm -ivh MySQL-server-5.5.54-1.linux2.6.x86_64.rpm
安装完成后, 出现如下警告, 需要为软件设置 root 用户的密码
③查看是否安装成功: mysqladmin –version
或者也可以通过 rpm 命令来查看:
④设置用户和密码: mysqladmin –u root password xxxxxx
1.3.3 Mysql 服务
1.3.3.1 Mysql 服务的启动和停止
查看状态: service mysql status
启动服务: service mysql start
停止服务: service mysql stop
重启服务: service mysql restart
启动之后, 查看进程:
1.3.3.2 Mysql 的安装位置
参数 | 路径 | 解释 | 备注 |
---|---|---|---|
–datadir | /var/lib/mysql/ | mysql 数据库文件的存放路径 | |
–basedir | /usr/bin | 相关命令目录 | mysqladmin mysqldump 等命令 |
–plugin-dir | /usr/lib64/mysql/plugin | mysql 插件存放路径 | |
–log-error | /var/lib/mysql/jack.atguigu.err | mysql 错误日志路径 | |
–pid-file | /var/lib/mysql/jack.atguigu.pid | 进程 pid 文件 | |
–socket | /var/lib/mysql/mysql.sock | 本地连接时用的 unix 套接字文件 | |
/usr/share/mysql | 配置文件目录 | mysql 脚本及配置文件 | |
/etc/init.d/mysql | 服务启停相关脚本 |
1.3.3.3 Mysql 服务的自启动
Mysql 服务是开机自动启动的!
如果要取消开机自启动, 则输入命令 ntsysv
1.3.3.4 Mysql 的重复启动问题
尝试去登录或者操作: 报错!
查看服务状态:
*解决: 杀死所有和 mysql 进程相关的操作, 然后重启服务! *
注意是 mysqld, d 代表 demon, 守护进程。
然后再重启:
1.3.4 修改字符集
1.3.4.1 常用命令
SQL 语句 | 描述 | 备注 |
---|---|---|
show databases | 列出所有数据库 | |
create database 库名 | 创建一个数据库 | |
create database 库名 character set utf8 | 创建数据库, 顺便执行字符集为 utf-8 | |
show create database 库名 | 查看数据库的字符集 | |
show variables like ‘%char%’ | 查询所有跟字符集相关的信息 | |
set [字符集属性]=utf8 | 设置相应的属性为 utf8 | 只是临时修改, 当前有效。 服务重启后, 失效。 |
alter database 库名 character set ‘utf8’ | 修改数据库的字符集 | |
alter table 表 名 convert to character set ‘utf8’ | 修改表的字符集 |
实验 SQL:
CREATE database mydb; |
---|
CREATE table mytable(id int,name varchar(30)); |
insert into mytable(id,name) values (1,’jack’); |
insert into mytable(id,name) values (2,’张三’) |
1.3.4.2 字符集乱码原因
如果在建库建表的时候, 没有明确指定字符集, 则采用默认的字符集 latin1,其中是不包含中文字符的。 查看默认的编码字符集:
1.3.4.3 永久修改
(1) 修改配置文件
在/usr/share/mysql/ 中找到 my.cnf 的配置文件, 拷贝其中的 my-huge.cnf 到 /etc/ 并命名为 my.cnf 。 添加以下内容后再重启服务。
注意: 必须将文件拷贝到指定路径, 且名称为 my.cnf
1 | [client] |
再次查看:
注意: 已经创建的数据库的设定不会发生变化, 参数修改只对新建的数据库有效!
(2) 修改已创建库、 表字符集
修改数据库的字符集
mysql> alter database mydb character set ‘utf8’;
修改数据表的字符集
mysql> alter table mytbl convert to character set ‘utf8’;
(3) 修改已经乱码数据
无论是修改 mysql 配置文件或是修改库、 表字符集, 都无法改变已经变成乱码的数据。只能删除数据重新插入或更新数据才可以完全解决
1.3.5 设置大小写不敏感
①查看大小写是否敏感: show variables like ‘%lower_case_table_names%’
windows 系统默认大小写不敏感, 但是 linux 系统是大小写敏感的
②设置大小写不敏感: 在 my.cnf 这个配置文件 [mysqld] 中加入 lower_case_table_names = 1 , 然后重启服务器
属性设置 | 描述 |
---|---|
0 | 大小写敏感 |
1 | 大小写不敏感。 创建的表, 数据库都是以小写形式存放在磁盘上, 对于 sql 语句都是转换为 小写对表和 DB 进行查找 |
2 | 创建的表和 DB 依据语句上格式存放, 凡是查找都是转换为小写进行 |
注意: 如果要设置属性为大小写不敏感, 要在重启数据库实例之前就需要将原来的数据库和表转换为小写, 否则将找不到数据库名。 在进行数据库参数设置之前, 需要掌握这个参数带来的影响, 切不可盲目设置。
1.3.6 sql_mode
sql_mode 定义了对 Mysql 中 sql 语句语法的校验规则!
sql_mode 是个很容易被忽视的变量, 默认值是空值, 在这种设置下是可以允许一些非法操作的, 比如允许一些
非法数据的插入。 在生产环境必须将这个值设置为严格模式, 所以开发、 测试环境的数据库也必须要设置, 这样在开发测试阶段就可以发现问题。
1.3.6.1 sql_mode 常用的值
ONLY_FULL_GROUP_BY | 对于 GROUP BY 聚合操作, 如果在 SELECT 中的列, 没有在 GROUP BY 中出现, 那么这个 SQL 是不合法的, 因为列不在 GROUP BY 从句中 |
---|---|
NO_AUTO_VALUE_ON_ZERO | 该值影响自增长列的插入。 默认设置下, 插入 0 或 NULL 代表生成下一个自增 长值。 如果用户 希望插入的值为 0, 而该列又是自增长的, 那么这个选项就有 用了 |
STRICT_TRANS_TABLES | 在该模式下, 如果一个值不能插入到一个事务表中, 则中断当前的操作, 对非 事务表不做限制 |
NO_ZERO_IN_DATE | 在严格模式下, 不允许日期和月份为零 |
NO_ZERO_DATE | 设置该值, mysql 数据库不允许插入零日期, 插入零日期会抛出错误而不是警告 |
ERROR_FOR_DIVISION_BY_ZERO | 在 INSERT 或 UPDATE 过程中, 如果数据被零除, 则产生错误而非警告。 如 果 未给出该模式, 那么数据被零除时 MySQL 返回 NULL |
NO_AUTO_CREATE_USER | 禁止 GRANT 创建密码为空的用户 |
NO_ENGINE_SUBSTITUTION | 如果需要的存储引擎被禁用或未编译, 那么抛出错误。 不设置此值时, 用默认 的存储引擎替代, 并抛出一个异常 |
PIPES_AS_CONCAT | 将”||”视为字符串的连接操作符而非或运算符, 这和 Oracle 数据库是一样的, 也和字符串的拼接函数 Concat 相类似 |
ANSI_QUOTES | 启用 ANSI_QUOTES 后, 不能用双引号来引用字符串, 因为它被解释为识别符 |
ORACLE | 设 置 等 同 于 PIPES_AS_CONCAT, ANSI_QUOTES, IGNORE_SPACE,NO_KEY_OPTIONS, NO_TABLE_OPTIONS, NO_FIELD_OPTIONS,NO_AUTO_CREATE_USER |
1.3.6.2 查看和修改
①查看当前的 sql_mode: select @@sql_mode;
②sql_mode 的影响案例: group by 查询语法错误!
1 | CREATE TABLE mytbl2 (id INT,NAME VARCHAR(200),age INT,dept INT); |
查询每个 dept 中年龄最大的人:SELECT NAME,dept,MAX(age) FROM mytbl2 GROUP BY dept;
正 确 写 法 :
1 | SELECT id,name,ab.dept,ab.maxage FROM mytbl2 m INNER JOIN(SELECT dept,MAX(age)maxage FROM mytbl2 GROUP BY dept)ab ON ab.dept=m.dept AND m.age=ab.maxage; |
③临时修改 sql_mode: set @@sql_mode=’’;
④永久修改, 需要在配置文件 my.cnf 中修改:
[mysqld] 下添加 sql_mode=’’ 然后重启 mysql 即可
1.4 Mysql配置文件
1.4.1 二进制日志log-bin
主从复制及备份恢复
log-bin 中存放了所有的操作记录(写?),可以用于恢复。相当于 Redis 中的 AOF
my.cnf中的log-bin配置(默认关闭)
1.4.2 错误日志log-error
默认是关闭的,记录严重的警告和错误信息,每次启动和关闭的详细信息等。
1.4.3 慢查询日志log
可自定义“慢”的概念:0-10秒之间的一个数。
慢查询日志会将超过这个查询事件的查询记录下来,方便找到需要优化的 sql 。
用于优化sql语句是使用。
默认关闭,记录查询的sql语句,如果开启会减低mysql的整体性能,因为记录日志也是需要消耗系统资源的
1.4.4 数据文件
windows
….\MySQLServer5.5\data目录下很多数据库文件
linux
默认路径:/var/lib/mysql
每个目录代表一个同名的库
1.4.4.1 Myisam存放方式
show create table mydb 查看创建 mydb 表的基本信息,其中包括了数据引擎。
自带的库 mysql 库中所有的表都是以 MyIsam 引擎存的。通过 myisam 引擎存的表都是 一式三份,放在库同名的文件夹下 /var/lib/mysql
frm文件(framework) :存放表结构
myd文件(data) : 存放表数据
myi文件(index) : 存放表索引
1.4.4.2 innodb存放方式
ibdata1:Innodb引擎将所有表的的数据都存在这里面 /usr/share/mysql/ibdata1 而frm文件存放在库同名的包下
frm文件 : 存放表结构
单独存放
1 | set innodb_file_per_table=on |
设在为 on 后 单独以 table名.ibd 的文件名存储
1.4.5 如何配置
1.4.5.1 windows
my.ini文件(配置文件)
1.4.5.2 Linux
/etc/my.cnf文件(配置文件)
二、Mysql逻辑架构简介
1 整体架构图
和其它数据库相比, MySQL 有点与众不同, 它的架构可以在多种不同场景中应用并发挥良好作用。 主要体现在
存储引擎的架构上, 插件式的存储引擎架构将查询处理和其它的系统任务以及数据的存储提取相分离。 这种架构可以根据业务的需求和实际需要选择合适的存储引擎。
1.1 连接层
最上层是一些客户端和连接服务, 包含本地 sock 通信和大多数基于客户端/服务端工具实现的类似于 tcp/ip 的
通信。 主要完成一些类似于连接处理、 授权认证、 及相关的安全方案。 在该层上引入了线程池的概念, 为通过认证安全接入的客户端提供线程。 同样在该层上可以实现基于 SSL 的安全链接。 服务器也会为安全接入的每个客户端验证它所具有的操作权限。
1.2 服务层
Management Serveices & Utilities | 系统管理和控制工具 |
---|---|
SQL Interface: | SQL 接口。 接受用户的 SQL 命令, 并且返回用户需要查询的结果。 比如 select from 就是调用 SQL Interface |
Parser | 解析器。 SQL 命令传递到解析器的时候会被解析器验证和解析 |
Optimizer | 查询优化器。 SQL 语句在查询之前会使用查询优化器对查询进行优化, 比如有 where 条件时, 优化器来决定先投影还是先过滤。 |
Cache 和 Buffer | 查询缓存。 如果查询缓存有命中的查询结果, 查询语句就可以直接去查询缓存中取 数据。 这个缓存机制是由一系列小缓存组成的。 比如表缓存, 记录缓存, key 缓存, 权限缓存等 |
1.3.引擎层
存储引擎层, 存储引擎真正的负责了 MySQL 中数据的存储和提取, 服务器通过 API 与存储引擎进行通信。 不同的存储引擎具有的功能不同, 这样我们可以根据自己的实际需要进行选取。
1.4.存储层
数据存储层, 主要是将数据存储在运行于裸设备的文件系统之上, 并完成与存储引擎的交互。
2 show profile
2.1 开启 profile
查看 profile 是否开启: show variables like ‘%profiling%’
如果没有开启, 可以执行 set profiling=1 开启!
2.2 使用profile
执行 show prifiles 命令, 可以查看最近的几次查询。
根据 Query_ID,可以进一步执行 show profile cpu,block io for query Query_id 来查看 sql 的具体执行步骤。
2.3 大致的查询流程
mysql 的查询流程大致是:
mysql 客户端通过协议与 mysql 服务器建连接, 发送查询语句, 先检查查询缓存, 如果命中, 直接返回结果,
否则进行语句解析,也就是说, 在解析查询之前, 服务器会先访问查询缓存(query cache)——它存储 SELECT 语句以及相应的查询结果集。 如果某个查询结果已经位于缓存中, 服务器就不会再对查询进行解析、 优化、 以及执行。 它仅仅将缓存中的结果返回给用户即可, 这将大大提高系统的性能。
语法解析器和预处理: 首先 mysql 通过关键字将 SQL 语句进行解析, 并生成一颗对应的“解析树”。 mysql 解析器将使用 mysql 语法规则验证和解析查询; 预处理器则根据一些 mysql 规则进一步检查解析数是否合法。
查询优化器当解析树被认为是合法的了, 并且由优化器将其转化成执行计划。 一条查询可以有很多种执行方式,
最后都返回相同的结果。 优化器的作用就是找到这其中最好的执行计划。。
然后, mysql 默认使用的 BTREE 索引, 并且一个大致方向是:无论怎么折腾 sql, 至少在目前来说, mysql 最多只用到表中的一个索引。
2.4 SQL的执行顺序
手写的顺序:
真正执行的顺序:
随着 Mysql 版本的更新换代, 其优化器也在不断的升级, 优化器会分析不同执行顺序产生的性能消耗不同而动
态调整执行顺序。 下面是经常出现的查询顺序:
2.5 MyISAM和InnoDB
对比项 | MyISAM | InnoDB |
---|---|---|
外键 | 不支持 | 支持 |
事务 | 不支持 | 支持 |
行表锁 | 表锁, 即使操作一条记录也会锁住整个表, 不适合高并发的操作 | 行锁,操作时只锁某一行, 不对其它行有影响, 适合高并发的操作 |
缓存 | 只缓存索引, 不缓存真实数据 | 不仅缓存索引还要缓存真实数据, 对内存要求较高, 而且内 存大小对性能有决定性的影 |
关注点 | 读性能 | 并发写、 事务、 资源 |
默认安装 | Y | Y |
默认使用 | N | Y |
自 带 系 统 表 使用 | Y | N |
三 索引优化分析
性能下降SQL慢 执行时间长 等待时间长
查询数据过多
能不能拆,条件过滤尽量少
关联了太多的表,太多join
join 原理。用 A 表的每一条数据 扫描 B表的所有数据。所以尽量先过滤。
没有利用到索引
单值
复合 : 条件多时,可以建共同索引(混合索引)。混合索引一般会偶先使用。有些情况下,就算有索引具体执行时也不会被使用。
服务器调优及各个参数设置(缓冲、线程数等)(不重要DBA的工作)
常见通用的Join查询
常见的 Join 查询图
7种 jion 案例
建表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32CREATE TABLE `t_dept` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`deptName` VARCHAR(30) DEFAULT NULL,
`address` VARCHAR(40) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
CREATE TABLE `t_emp` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(20) DEFAULT NULL,
`age` INT(3) DEFAULT NULL,
`deptId` INT(11) DEFAULT NULL,
empno int not null,
PRIMARY KEY (`id`),
KEY `idx_dept_id` (`deptId`)
#CONSTRAINT `fk_dept_id` FOREIGN KEY (`deptId`) REFERENCES `t_dept` (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
INSERT INTO t_dept(deptName,address) VALUES('华山','华山');
INSERT INTO t_dept(deptName,address) VALUES('丐帮','洛阳');
INSERT INTO t_dept(deptName,address) VALUES('峨眉','峨眉山');
INSERT INTO t_dept(deptName,address) VALUES('武当','武当山');
INSERT INTO t_dept(deptName,address) VALUES('明教','光明顶');
INSERT INTO t_dept(deptName,address) VALUES('少林','少林寺');
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('风清扬',90,1,100001);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('岳不群',50,1,100002);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('令狐冲',24,1,100003);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('洪七公',70,2,100004);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('乔峰',35,2,100005);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('灭绝师太',70,3,100006);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('周芷若',20,3,100007);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('张三丰',100,4,100008);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('张无忌',25,5,100009);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('韦小宝',18,null,100010);
mysql 不支持 outer join 语法
索引优化分析
索引的概念
MySQL 官方对索引的定义为: 索引(Index) 是帮助 MySQL 高效获取数据的数据结构。 可以得到索引的本质:索引是数据结构。 可以简单理解为排好序的快速查找数据结构。
在数据之外, 数据库系统还维护着满足特定查找算法的数据结构, 这些数据结构以某种方式引用(指向) 数据,这样就可以在这些数据结构上实现高级查找算法。 这种数据结构, 就是索引。 下图就是一种可能的索引
方式示例:
左边是数据表, 一共有两列七条记录, 最左边的是数据记录的物理地址。 为了加快 Col2 的查找, 可以维护一个右边所示的二叉查找树, 每个节点分别包含索引键值和一个指向对应数据记录物理地址的指 针, 这样就可以运用二叉查找在一定的复杂度内获取到相应数据, 从而快速的检索出符合条件的记录。
一般来说索引本身也很大, 不可能全部存储在内存中, 因此索引往往以索引文件的形式存储的磁盘上。
优缺点
优势:
提高数据检索的效率, 降低数据库的IO成本
通过索引列对数据进行排序, 降低数据排序的成本, 降低了CPU的消耗。
劣势:
虽然索引大大提高了查询速度, 同时却会降低更新表的速度, 如对表进行INSERT、 UPDATE和DELETE。 因为更新表时, MySQL不仅要保存数据, 还要保存一下索引文件每次更新添加了索引列的字段, 都会调整因为
更新所带来的键值变化后的索引信息。实际上索引也是一张表, 该表保存了主键与索引字段, 并指向实体表的记录, 所以索引列也是要占用空间的。
Mysql的索引
Btree索引
MySQL 使用的是 Btree 索引。
【初始化介绍】
一颗 b 树, 浅蓝色的块我们称之为一个磁盘块, 可以看到每个磁盘块包含几个数据项(深蓝色所示) 和指针(黄色所示),如磁盘块 1 包含数据项 17 和 35, 包含指针 P1、 P2、 P3, P1 表示小于 17 的磁盘块, P2 表示在 17 和 35 之间的磁盘块, P3 表示大于 35 的磁盘块。 真实的数据存在于叶子节点即 3、 5、 9、 10、 13、 15、 28、 29、 36、 60、 75、 79、 90、 99。 非叶子节点只不存储真实的数据, 只存储指引搜索方向的数据项, 如 17、 35 并不真实存在于数据表中。
【查找过程】
如果要查找数据项 29, 那么首先会把磁盘块 1 由磁盘加载到内存, 此时发生一次 IO, 在内存中用二分查找确定 29 在 17 和 35 之间, 锁定磁盘块 1 的 P2 指针, 内存时间因为非常短(相比磁盘的 IO) 可以忽略不计, 通过磁盘块 1 的 P2 指针的磁盘地址把磁盘块 3 由磁盘加载到内存, 发生第二次 IO, 29 在 26 和 30 之间, 锁定磁盘块 3 的 P2 指针, 通过指针加载磁盘块 8 到内存, 发生第三次 IO, 同时内存中做二分查找找到 29, 结束查询, 总计三次 IO。
B+tree索引
B+Tree 与 B-Tree 的区别
1) B-树的关键字和记录是放在一起的, 叶子节点可以看作外部节点, 不包含任何信息; B+树的非叶子节点中只有关键字和指向下一个节点的索引, 记录只放在叶子节点中。
2) 在 B-树中, 越靠近根节点的记录查找时间越快, 只要找到关键字即可确定记录的存在; 而 B+树中每个记录的查找时间基本是一样的, 都需要从根节点走到叶子节点, 而且在叶子节点中还要再比较关键字。 从这个角度看 B-树的性能好像要比 B+树好, 而在实际应用中却是 B+树的性能要好些。 因为 B+树的非叶子节点不存放实际的数据,这样每个节点可容纳的元素个数比 B-树多, 树高比 B-树小, 这样带来的好处是减少磁盘访问次数。 尽管 B+树找到一个记录所需的比较次数要比 B-树多, 但是一次磁盘访问的时间相当于成百上千次内存比较的时间, 因此实际中B+树的性能可能还会好些, 而且 B+树的叶子节点使用指针连接在一起, 方便顺序遍历(例如查看一个目录下的所有文件, 一个表中的所有记录等), 这也是很多数据库和文件系统使用 B+树的缘故。
思考: 为什么说 B+树比 B-树更适合实际应用中操作系统的文件索引和数据库索引?
1) B+树的磁盘读写代价更低
B+树的内部结点并没有指向关键字具体信息的指针。 因此其内部结点相对 B 树更小。 如果把所有同一内部结点的关键字存放在同一盘块中, 那么盘块所能容纳的关键字数量也越多。 一次性读入内存中的需要查找的关键字也就越多。 相对来说 IO 读写次数也就降低了。
2) B+树的查询效率更加稳定
由于非终结点并不是最终指向文件内容的结点, 而只是叶子结点中关键字的索引。 所以任何关键字的查找必须走一条从根结点到叶子结点的路。 所有关键字查询的路径长度相同, 导致每一个数据的查询效率相当。
聚簇索引和非聚簇索引
聚簇索引并不是一种单独的索引类型, 而是一种数据存储方式。 术语‘聚簇’ 表示数据行和相邻的键值聚簇的存储在一起。 如下图, 左侧的索引就是聚簇索引, 因为数据行在磁盘的排列和索引排序保持一致。
聚簇索引的好处:
按照聚簇索引排列顺序, 查询显示一定范围数据的时候, 由于数据都是紧密相连, 数据库不不用从多个数据块中提取数据, 所以节省了大量的 io 操作。
聚簇索引的限制:
对于 mysql 数据库目前只有 innodb 数据引擎支持聚簇索引, 而 Myisam 并不支持聚簇索引。由于数据物理存储排序方式只能有一种, 所以每个 Mysql 的表只能有一个聚簇索引。 一般情况下就是该表的主键。
为了充分利用聚簇索引的聚簇的特性, 所以 innodb 表的主键列尽量选用有序的顺序 id, 而不建议用无序的 id, 比如 uuid 这种。
full-text全文索引
全文索引(也称全文检索)是目前搜索引擎使用的一种关键技术。它能够利用【分词技术】等多种算法智能分析出文本文字中关键词的频率和重要性,然后按照一定的算法规则智能地筛选出我们想要的搜索结果。
1
2
3
4
5
6
7CREATE TABLE `article` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(200) DEFAULT NULL,
`content` text,
PRIMARY KEY (`id`),
FULLTEXT KEY `title` (`title`,`content`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;1
2
3
4
5不同于like方式的的查询:
SELECT * FROM article WHERE content LIKE ‘%查询字符串%’;
全文索引用match+against方式查询:
SELECT * FROM article WHERE MATCH(title,content) AGAINST (‘查询字符串’);
明显的提高查询效率。限制:
mysql5.6.4以前只有Myisam支持,5.6.4版本以后innodb才支持,但是官方版本不支持中文分词,需要第三方分词插件。5.7以后官方支持中文分词。
随着大数据时代的到来,关系型数据库应对全文索引的需求已力不从心,逐渐被 solr,elasticSearch等专门的搜索引擎所替代。
Hash索引
Hash索引只有Memory, NDB两种引擎支持,Memory引擎默认支持Hash索引,如果多个hash值相同,出现哈希碰撞,那么索引以链表方式存储。
NoSql采用此中索引结构。R-Tree索引
R-Tree在mysql很少使用,仅支持geometry数据类型,支持该类型的存储引擎只有myisam、bdb、innodb、ndb、archive几种。
相对于b-tree,r-tree的优势在于范围查找。
时间复杂度(扩展)
同一问题可用不同算法解决, 而一个算法的质量优劣将影响到算法乃至程序的效率。 算法分析的目的在于选择合适算法和改进算法。
Mysql 索引分类
单值索引
概念: 即一个索引只包含单个列, 一个表可以有多个单列索引
语法:
所表一起创建:
1
2
3
4
5CREATE TABLE customer (id INT(10) UNSIGNED AUTO_INCREMENT ,customer_no VARCHAR(200),customer_name
VARCHAR(200),
PRIMARY KEY(id),
KEY (customer_name)
);单独建单值索引:
1
CREATE INDEX idx_customer_name ON customer(customer_name);
概念: 索引列的值必须唯一, 但允许有空值
随表一起创建:
1
2
3
4
5
6CREATE TABLE customer (id INT(10) UNSIGNED AUTO_INCREMENT ,customer_no VARCHAR(200),customer_name
VARCHAR(200),
PRIMARY KEY(id),
KEY (customer_name),
UNIQUE (customer_no)
);单独建唯一索引:
1
CREATE UNIQUE INDEX idx_customer_no ON customer(customer_no);
主键索引
概念: 设定为主键后数据库会自动建立索引, innodb为聚簇索引
随表一起建索引
1
2
3
4CREATE TABLE customer (id INT(10) UNSIGNED AUTO_INCREMENT ,customer_no VARCHAR(200),customer_name
VARCHAR(200),
PRIMARY KEY(id)
);单独建主键索引:
1
ALTER TABLE customer add PRIMARY KEY customer(customer_no);
删除建主键索引
1
ALTER TABLE customer drop PRIMARY KEY ;
修改建主键索引:
1
必须先删除掉(drop)原索引, 再新建(add)索引
概念: 即一个索引包含多个列
随表一起建索引:
1
2
3
4
5
6
7CREATE TABLE customer (id INT(10) UNSIGNED AUTO_INCREMENT ,customer_no VARCHAR(200),customer_name
VARCHAR(200),
PRIMARY KEY(id),
KEY (customer_name),
UNIQUE (customer_name),
KEY (customer_no,customer_name)
);单独建索引:
1
CREATE INDEX idx_no_name ON customer(customer_no,customer_name);
基本语法
操作 命令 创建 CREATE [UNIQUE ] INDEX [indexName] ON table_name(column)) 删除 DROP INDEX [indexName] ON mytable; 查看 SHOW INDEX FROM table_name\G 使 用 Alter 命令 ALTER TABLE tbl_name ADD PRIMARY KEY (column_list) : 该语句添加一个主键, 这意味着索引值必须是唯一 的, 且不能为 NULL。 ALTER TABLE tbl_name ADD PRIMARY KEY (column_list) ALTER TABLE tbl_name ADD INDEX index_name (column_list): 添加普通索引, 索引值可出现多次。 ALTER TABLE tbl_name ADD FULLTEXT index_name (column_list):该语句指定了索引为 FULLTEXT , 用于全文索 引。
索引的创建时机
适合创建索引的情况
- 主键自动建立唯一索引;
- 频繁作为查询条件的字段应该创建索引
- 查询中与其它表关联的字段, 外键关系建立索引
- 单键/组合索引的选择问题, 组合索引性价比更高
- 查询中排序的字段, 排序字段若通过索引去访问将大大提高排序速度
- 查询中统计或者分组字段
不适合创建索引的情
- 表记录太少
- 经常增删改的表或者字段
- Where 条件里用不到的字段不创建索引
- 过滤性不好的不适合建索引 (数据重复且分布平均的表字段)
注意,如果某个数据列包含许多重复的内容,为它建立索引就没有太大的实际效果。
四、Explain性能分析
MySQL常见瓶颈
CPU :SQL中对大量数据进行比较、关联、排序、分组(SQL中对大量数据进行比较、关联、排序、分组)
CPU在饱和的时候一般发生在数据装入内存或从磁盘上读取数据的时候
IO:磁盘IO瓶颈发生在装入数据远大于内存容量的时候
锁: 不适宜的锁的设置,导致线程阻塞,性能下降。|死锁,线程之间交叉调用资源,导致死锁,程序卡住。
服务器硬件的性能瓶颈:top,free, iostat和vmstat来查看系统的性能状态
Explain
是什么(查看执行计划)
使用EXPLAIN关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理你的SQL语句的。分析你的查询语句或是表结构的性能瓶颈
能干嘛
- 表的读取顺序
- 哪些索引可以使用
- 数据读取操作的操作类型
- 哪些索引被实际使用
- 表之间的引用
- 每张表有多少行被优化器查询
怎么玩
用法:Explain + SQL语句
Explain 执行后返回的信息:
字段解释
id
select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序
id 相同, 执行顺序由上至下
id 不同, 如果是子查询, id 的序号会递增, id 值越大优先级越高, 越先被执行
有相同也有不同
id 如果相同, 可以认为是一组, 从上往下顺序执行; 在所有组中, id 值越大, 优先级越高, 越先执行
衍生 = DERIVED
关注点: id 号每个号码, 表示一趟独立的查询。 一个 sql 的查询趟数越少越好。
select_type
select_type 代表查询的类型, 主要是用于区别普通查询、 联合查询、 子查询等的复杂查询。
select_type 属性 | 含义 |
---|---|
SIMPLE | 简单的 select 查询,查询中不包含子查询或者 UNION |
PRIMARY | 查询中若包含任何复杂的子部分, 最外层查询则被标记为 Primary |
DERIVED | 在 FROM 列表中包含的子查询被标记为 DERIVED(衍生) MySQL 会递归执行这些子查询, 把结果放在临时表里。 |
SUBQUERY | 在SELECT或WHERE列表中包含了子查询 |
DEPEDENT SUBQUERY | 在SELECT或WHERE列表中包含了子查询,子查询基于外层 |
UNCACHEABLE SUBQUERY | 无法使用缓存的子查询 |
UNION | 若第二个SELECT出现在UNION之后, 则被标记为UNION; 若UNION包含在FROM子句的子查询中,外层SELECT将被标记为: DERIVED |
UNION RESULT | 从UNION表获取结果的SELECT |
SIMPLE
SIMPLE 代表单表查询;
PRIMARY
查询中若包含任何复杂的子部分, 最外层查询则被标记为 Primary。
DERIVED
在 FROM 列表中包含的子查询被标记为 DERIVED(衍生),MySQL 会递归执行这些子查询, 把结果放在临时表里。
SUBQUERY
在 SELECT 或 WHERE 列表中包含了子查询。
DEPENDENT SUBQUERY
在 SELECT 或 WHERE 列表中包含了子查询,子查询基于外层。
都是 where 后面的条件, subquery 是单个值, dependent subquery 是一组值。
UNCACHEABLE SUBQUREY
UNION
若第二个 SELECT 出现在 UNION 之后, 则被标记为 UNION; 若 UNION 包含在 FROM 子句的子查询中,外层 SELECT将被标记为: DERIVED。
UNION RESULT
从 UNION 表获取结果的 SELECT。
table
这个数据是基于哪张表的。
type
type 是查询的访问类型。 是较为重要的一个指标, 结果值从最好到最坏依次是:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL , 一般来说, 得保证查询至少达到 range 级别, 最好能达到 ref。
常见:system>const>eq_ref>ref>range>index>ALL
system
表只有一行记录(等于系统表), 这是 const 类型的特列, 平时不会出现, 这个也可以忽略不计
const
表示通过索引一次就找到了,const 用于比较 primary key 或者 unique 索引。 因为只匹配一行数据, 所以很快如将主键置于 where 列表中, MySQL 就能将该查询转换为一个常量。
eq_ref
唯一性索引扫描, 对于每个索引键, 表中只有一条记录与之匹配。 常见于主键或唯一索引扫描。
ref
非唯一性索引扫描, 返回匹配某个单独值的所有行.本质上也是一种索引访问, 它返回所有匹配某个单独值的行,然而, 它可能会找到多个符合条件的行, 所以他应该属于查找和扫描的混合体
没用索引前:
建立索引后:
range
只检索给定范围的行,使用一个索引来选择行。 key 列显示使用了哪个索引一般就是在你的 where 语句中出现了 between、 <、 >、 in 等的查询这种范围扫描索引扫描比全表扫描要好, 因为它只需要开始于索引的某一点, 而结束语另一点, 不用扫描全部索引。
index
出现index是sql使用了索引但是没用通过索引进行过滤, 一般是使用了覆盖索引或者是利用索引进行了排序分组。
all
Full Table Scan, 将遍历全表以找到匹配的行。
index_merge
在查询过程中需要多个索引组合使用, 通常出现在有 or 的关键字的 sql 中。
ref_or_null
对于某个字段既需要关联条件, 也需要 null 值得情况下。 查询优化器会选择用 ref_or_null 连接查询。
index_subquery
利用索引来关联子查询, 不再全表扫描。
unique_subquery
该联接类型类似于 index_subquery。 子查询中的唯一索引。
possible_keys
显示可能应用在这张表中的索引, 一个或多个。 查询涉及到的字段上若存在索引, 则该索引将被列出, 但不一定被查询实际使用。
key
实际使用的索引。 如果为NULL, 则没有使用索引。
key_len
表示索引中使用的字节数, 可通过该列计算查询中使用的索引的长度。 key_len 字段能够帮你检查是否充分的利用上了索引。 ken_len 越长, 说明索引使用的越充分。
如何计算:
- 先看索引上字段的类型+长度比如 int=4 ; varchar(20) =20 ; char(20) =20
- 如果是 varchar 或者 char 这种字符串字段, 视字符集要乘不同的值, 比如 utf-8 要乘 3,GBK 要乘 2,
- varchar 这种动态字符串要加 2 个字节
- 允许为空的字段要加 1 个字节
第一组: key_len=age 的字节长度+name 的字节长度=4+1 + ( 20*3+2)=5+62=67
第二组: key_len=age 的字节长度=4+1=5
ref
显示索引的哪一列被使用了, 如果可能的话, 是一个常数。 哪些列或常量被用于查找索引列上的值。
rows
rows 列显示 MySQL 认为它执行查询时必须检查的行数。 越少越好!
Extra
其他的额外重要的信息
Using filesort
说明 mysql 会对数据使用一个外部的索引排序, 而不是按照表内的索引顺序进行读取。 MySQL 中无法利用索引完成的排序操作称为“文件排序”。
出现 filesort 的情况:
优化后, 不再出现 filesort 的情况:
查询中排序的字段, 排序字段若通过索引去访问将大大提高排序速度。
Using temporary
使了用临时表保存中间结果,MySQL 在对查询结果排序时使用临时表。 常见于排序 order by 和分组查询 group by。
优化前:
优化后:
Using index
Using index 代表表示相应的 select 操作中使用了覆盖索引(Covering Index), 避免访问了表的数据行, 效率不错!
如果同时出现 using where, 表明索引被用来执行索引键值的查找;
如果没有同时出现 using where, 表明索引只是用来读取数据而非利用索引执行查找。 利用索引进行了排序或分组。
Using where
表明使用了 where 过滤
Using join buffer
使用了连接缓存。
impossible where
where 子句的值总是 false, 不能用来获取任何元组。
select tables optimized away
在没有 GROUPBY 子句的情况下, 基于索引优化 MIN/MAX 操作或者对于 MyISAM 存储引擎优化 COUNT(*)操作, 不必等到执行阶段再进行计算, 查询执行计划生成的阶段即完成优化。
在 innodb 中:
在 Myisam 中:
五、索引优化
索引失效
全值匹配我最爱
有以下 SQL 语句
1
2
3EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 and deptid=4
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 and deptid=4 AND emp.name = 'abcd'建立索引
CREATE INDEX idx_age_deptid_name ON emp(age,deptid,NAME);
结论: 全职匹配我最爱指的是, 查询的字段按照顺序在索引中都可以匹配到!
SQL 中查询字段的顺序, 跟使用索引中字段的顺序, 没有关系。 优化器会在不影响 SQL 执行结果的前提下, 给你自动地优化。
最佳左前缀法则
查询字段与索引字段顺序的不同会导致, 索引无法充分使用, 甚至索引失效!
原因: 使用复合索引, 需要遵循最佳左前缀法则, 即如果索引了多列, 要遵守最左前缀法则。 指的是查询从索引的最左前列开始并且不跳过索引中的列。
结论: 过滤条件要使用索引必须按照索引建立时的顺序, 依次满足, 一旦跳过某个字段, 索引后面的字段都无法被使用。
不要在索引列上做任何计算
不在索引列上做任何操作(计算、 函数、 (自动 or 手动)类型转换), 会导致索引失效而转向全表扫描。
在查询列上使用了函数
1
2EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE age=30;
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE LEFT(age,3)=30;结论: 等号左边无计算!
*在查询列上做了转换 *
1
2
3create index idx_name on emp(name);
explain select sql_no_cache * from emp where name='30000';
explain select sql_no_cache * from emp where name=30000;字符串不加单引号, 则会在 name 列上做一次转换!
结论: 等号右边无转换!
索引列上不能有范围查询
1
2explain SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 and deptid=5 AND emp.name = 'abcd';
explain SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 and deptid<=5 AND emp.name = 'abcd';*建议: 将可能做范围查询的字段的索引顺序放在最后 *
尽量使用覆盖索引
即查询列和索引列一直, 不要写 select *!
1
2explain SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 and deptId=4 and name='XamgXt';
explain SELECT SQL_NO_CACHE age,deptId,name FROM emp WHERE emp.age=30 and deptId=4 and name='XamgXt';使用不等于(!= 或者<>)的时候
mysql 在使用不等于(!= 或者<>)时, 有时会无法使用索引会导致全表扫描。
字段的 is not null 和 is null
当字段允许为 Null 的条件下:
is not null 用不到索引, is null 可以用到索引。
like 的前后模糊匹配
前缀不能出现模糊匹配!
如果要加在两边 可以使用覆盖索引
字符串不加单引号索引失效
底层进行转换使索引失效,使用了函数造成索引失效
减少使用 or
使用 union all 或者 union 来替代:
练习
假设 index(a,b,c);
Where 语句 索引是否被使用 where a = 3 Y,使用到 a where a = 3 and b = 5 Y,使用到 a, b where a = 3 and b = 5 and c = 4 Y,使用到 a,b,c where b = 3 或者 where b = 3 and c = 4 或者 where c = 4 N where a = 3 and c = 5 使用到 a, 但是 c 不可以, b 中间断了 where a = 3 and b > 4 and c = 5 使用到 a 和 b, c 不能用在范围之后, b 断了 where a is null and b is not null is null 支持索引 但是 is not null 不支持,所 以 a 可以使用索引,但是 b 不可以使用 where a <> 3 不能使用索引 where abs(a) =3 不能使用 索引 where a = 3 and b like ‘kk%’ and c = 4 Y,使用到 a,b,c where a = 3 and b like ‘%kk’ and c = 4 Y,只用到 a where a = 3 and b like ‘%kk%’ and c = 4 Y,只用到 a where a = 3 and b like ‘k%kk%’ and c = 4 Y,使用到 a,b,c
法则:
全职匹配我最爱, 最左前缀要遵守;
带头大哥不能死, 中间兄弟不能断;
索引列上少计算, 范围之后全失效;
LIKE 百分写最右, 覆盖索引不写*;
不等空值还有 OR, 索引影响要注意;
VAR 引号不可丢, SQL 优化有诀窍。
示例:
1 | 【建表语句】 |
一般性建议:
- 对于单键索引,尽量选择针对当前query过滤性更好的索引
- 在选择组合索引的时候,当前Query中过滤性最好的字段在索引字段顺序中,位置越靠前越好。(避免索引过滤性好的索引失效)
- 在选择组合索引的时候,尽量选择可以能够包含当前query中的where字句中更多字段的索引
- 尽可能通过分析统计信息和调整query的写法来达到选择合适索引的目的
子查询优化
用in 还是 exists
1 | 有索引 大表驱动小表 |
1 | 有索引 小表驱动大表 |
有索引小驱动大表 性能优于 大表驱动小表
1 | 无索引 小表驱动大表 |
1 | 无索引大表驱动小表 |
结论:
有索引的情况下 用 inner join 是最好的 其次是 in ,exists最糟糕
无索引的情况下用 小表驱动大表
因为join 方式需要distinct ,没有索引distinct消耗性能较大
所以 exists性能最佳 in其次 join性能最差?
无索引的情况下大表驱动小表
in 和 exists 的性能应该是接近的 都比较糟糕 exists稍微好一点 超不过5% 但是inner join 优于使用了 join buffer 所以快很多 如果left join 则最慢
order by关键字优化
- ORDER BY子句,尽量使用Index方式排序,避免使用FileSort方式排序
- 尽可能在索引列上完成排序操作,遵照索引建的最佳左前缀
- 如果不在索引列上,filesort有两种算法:mysql就要启动双路排序和单路排序
案例:
无过滤不索引
1
2
3
4create index idx_age_deptid_name on emp (age,deptid,name);
explain select * from emp where age=40 order by deptid;
explain select * from emp order by age,deptid;
explain select * from emp order by age,deptid limit 10;using filesort 说明进行了手工排序! 原因在于没有 where 作为过滤条件!
结论: 无过滤, 不索引。 where, limt 都相当于一种过滤条件, 所以才能使用上索引!
顺序错, 必排序
① explain select * from emp where age=45 order by deptid,name;
② explain select * from emp where age=45 order by deptid,empno;
empno 字段并没有建立索引, 因此也无法用到索引, 此字段需要排序!
③explain select * from emp where age=45 order by name,deptid;
where 两侧列的顺序可以变换, 效果相同, 但是 order by 列的顺序不能随便变换!
④explain select * from emp where deptid=45 order by age;
deptid 作为过滤条件的字段, 无法使用索引, 因此排序没法用上索引
方向反, 必排序
①explain select * from emp where age=45 order by deptid desc, name desc ;
如果可以用上索引的字段都使用正序或者逆序, 实际上是没有任何影响的, 无非将结果集调换顺序。
②explain select * from emp where age=45 order by deptid asc, name desc ;
如果排序的字段, 顺序有差异, 就需要将差异的部分, 进行一次倒置顺序, 因此还是需要手动排序的!
索引的选择
①首先, 清除 emp 上面的所有索引, 只保留主键索引!
drop index idx_age_deptid_name on emp;
②查询: 年龄为 30 岁的, 且员工编号小于 101000 的用户, 按用户名称排序
explain SELECT SQL_NO_CACHE * FROM emp WHERE age =30 AND empno <101000 ORDER BY NAME ;
③全表扫描肯定是不被允许的, 因此我们要考虑优化。
思路: 首先需要让 where 的过滤条件, 用上索引;
查询中, age.empno 是查询的过滤条件, 而 name 则是排序的字段, 因此我们来创建一个此三个字段的复合索引:
create index idx_age_empno_name on emp(age,empno,name);
再次查询, 发现 using filesort 依然存在。
原因: empno 是范围查询, 因此导致了索引失效, 所以 name 字段无法使用索引排序。
所以, 三个字段的符合索引, 没有意义, 因为 empno 和 name 字段只能选择其一!
④解决: 鱼与熊掌不可兼得, 因此, 要么选择 empno,要么选择 name
1
2
3drop index idx_age_empno_name on emp;
create index idx_age_name on emp(age,name);
create index idx_age_empno on emp(age,empno);两个索引同时存在, mysql 会选择哪个?
explain SELECT SQL_NO_CACHE * FROM emp use index(idx_age_name) WHERE age =30 AND empno <101000 ORDER BY NAME ;
原因: 所有的排序都是在条件过滤之后才执行的, 所以如果条件过滤了大部分数据的话, 几百几千条数据进行排序其实并不是很消耗性能, 即使索引优化了排序但实际提升性能很有限。 相对的 empno<101000 这个条件如果没有用到索引的话, 要对几万条的数据进行扫描, 这是非常消耗性能的, 使用 empno 字段的范围查询, 过滤性更好(empno 从 100000 开始)!
结论: 当范围条件和 group by 或者 order by 的字段出现二选一时 , 优先观察条件字段的过滤数量, 如果过滤的数据足够多, 而需要排序的数据并不多时, 优先把索引放在范围字段上。 反之, 亦然。
using filesort
*mysql 的排序算法 *
①双路排序
MySQL 4.1 之前是使用双路排序,字面意思就是两次扫描磁盘, 最终得到数据, 读取行指针和 orderby 列, 对他们进行排序, 然后扫描已经排序好的列表, 按照列表中的值重新从列表中读取对应的数据输出。
从磁盘取排序字段, 在 buffer 进行排序, 再从磁盘取其他字段。
简单来说, 取一批数据, 要对磁盘进行了两次扫描, 众所周知, I\O 是很耗时的, 所以在 mysql4.1 之后, 出现了第二种改进的算法, 就是单路排序。
②单路排序
从磁盘读取查询需要的所有列, 按照 order by 列在 buffer 对它们进行排序, 然后扫描排序后的列表进行输出,它的效率更快一些, 避免了第二次读取数据。 并且把随机 IO 变成了顺序 IO,但是它会使用更多的空间,
因为它把每一行都保存在内存中了。③单路排序的问题
由于单路是后出的, 总体而言好过双路。 但是存在以下问题:
在 sort_buffer 中, 方法 B 比方法 A 要多占用很多空间, 因为方法 B 是把所有字段都取出, 所以有可能取出的数据的总大小超出了 sort_buffer 的容量, 导致每次只能取 sort_buffer 容量大小的数据, 进行排序(创建 tmp 文件, 多路合并), 排完再取取 sort_buffer 容量大小, 再排……从而多次 I/O。
结论: 本来想省一次 I/O 操作, 反而导致了大量的 I/O 操作, 反而得不偿失。
*如何优化 *
①增大 sort_butter_size 参数的设置
不管用哪种算法, 提高这个参数都会提高效率, 当然, 要根据系统的能力去提高, 因为这个参数是针对每个进程的 1M-8M 之间调整。
②增大 max_length_for_sort_data 参数的设置
mysql 使用单路排序的前提是排序的字段大小要小于 max_length_for_sort_data。
提高这个参数, 会增加用改进算法的概率。 但是如果设的太高, 数据总容量超出 sort_buffer_size 的概率就增大,明显症状是高的磁盘 I/O 活动和低的处理器使用率。(1024-8192 之间调整)。
③减少 select 后面的查询的字段。
当 Query 的字段大小总和小于 max_length_for_sort_data 而且排序字段不是 TEXT|BLOB 类型时, 会用改进后的算法——单路排序, 否则用老算法——多路排序。
两种算法的数据都有可能超出 sort_buffer 的容量, 超出之后, 会创建 tmp 文件进行合并排序, 导致多次 I/O,但是用单路排序算法的风险会更大一些,所以要提高 sort_buffer_size。
使用覆盖索引
覆盖索引: SQL 只需要通过索引就可以返回查询所需要的数据, 而不必通过二级索引查到主键之后再去查询数据。
group by
group by 使用索引的原则几乎跟 order by 一致 , 唯一区别是 groupby 即使没有过滤条件用到索引, 也可以直接使用索引。
group by实质是先排序后进行分组,遵照索引建的最佳左前缀
当无法使用索引列,增大max_length_for_sort_data参数的设置+增大sort_buffer_size参数的设置
where高于having,能写在where限定的条件就不要去having限定了。
总结:
六、查询截取分析
慢查询日志
1.1 是什么
(1) MySQL的慢查询日志是MySQL提供的一种日志记录, 它用来记录在MySQL中响应时间超过阀值的语句, 具体指运行时间超过long_query_time值的SQL, 则会被记录到慢查询日志中。
(2) 具体指运行时间超过long_query_time值的SQL, 则会被记录到慢查询日志中。 long_query_time的默认值为10, 意思是运行10秒以上的语句。
(3) 由他来查看哪些SQL超出了我们的最大忍耐时间值, 比如一条sql执行超过5秒钟, 我们就算慢SQL, 希望能收集超过5秒的sql, 结合之前explain进行全面分析。
1.2 怎么用
默认情况下, MySQL 数据库没有开启慢查询日志, 需要我们手动来设置这个参数。
当然, 如果不是调优需要的话, 一般不建议启动该参数, 因为开启慢查询日志会或多或少带来一定的性能影响。
慢查询日志支持将日志记录写入文件。
(1) 开启设置
SQL 语句 | 描述 | 备注 |
---|---|---|
SHOW VARIABLES LIKE ‘%slow_query_log%’; | 查看慢查询日志是否开启 | 默认情况下 slow_query_log 的值为 OFF, 表示慢查询日志是禁用的 |
set global slow_query_log=1; | 开启慢查询日志 | |
SHOW VARIABLES LIKE ‘long_query_time%’; | 查看慢查询设定阈值 | 单位秒 |
set long_query_time=1 | 设定慢查询阈值 | 单位秒 |
(2) 如永久生效需要修改配置文件 my.cnf 中[mysqld]下配置
1 | [mysqld] |
(3) 运行查询时间长的 sql, 打开慢查询日志查看
1.3日志分析工具mysqldumpslow
(1) 查看mysqldumpslow的帮助信息
命令:[root@cocoon ~]# mysqldumpslow –help
参数 | 描述 | 备注 |
---|---|---|
-s | 是表示按照何种方式排序 | |
c | 访问次数 | |
l | 锁定时间 | |
r | 返回记录 | |
t | 查询时间 | |
al | 平均锁定时间 | |
ar | 平均返回记录数 | |
at | 平均查询时间 | |
-t | 即为返回前面多少条的数据 | |
-g | 后边搭配一个正则匹配模式, 大小写不敏感的 |
(2) 查看mysqldumpslow的帮助信息
1 | 得到返回记录集最多的 10 个 SQL |
批量数据脚本
插入数据
1.1 建表语句
1 | CREATE TABLE `dept` ( |
1.2 设置参数
在执行创建函数之前, 首先请保证 log_bin_trust_function_creators 参数为 1, 即 on 开启状态。
否则会报错:
查询: show variables like ‘log_bin_trust_function_creators’;
设置: set global log_bin_trust_function_creators=1;
当然, 如上设置只存在于当前操作, 想要永久生效, 需要写入到配置文件中:
在[mysqld]中加上 log_bin_trust_function_creators=1
1.3 编写随机函数
创建函数, 保证每条数据都不同。
1.3.1 随机产生字符串
1 | DELIMITER $$ |
如果要删除函数, 则执行: drop function rand_string;
1.3.2 随机产生部门编号
1 | #用于随机产生多少到多少的编号 |
如果要删除函数: drop function rand_num;
1.4 创建存储过程
1.4.1 创建往 emp 表中插入数据的存储过程
1 | DELIMITER $$ |
1.4.2 创建往 dept 表中插入数据的存储过程
1 | #执行存储过程, 往 dept 表添加随机数据 |
1.5 调用存储过程
1.5.1 添加数据到部门表
1 | #执行存储过程, 往 dept 表添加 1 万条数据 |
1.5.2 添加数据到员工表
1 | #执行存储过程, 往 emp 表添加 50 万条数据 |
1.6 批量删除某个表上的所有索引
1.6.1 删除索引的存储过程
1 | DELIMITER $$ |
1.6.2 执行存储过程
调用: CALL proc_drop_index(“dbname”,”tablename”);
Show Profile
是什么:是mysql提供可以用来分析当前会话中语句执行的资源消耗情况。可以用于SQL的调优的测量
官网:http://dev.mysql.com/doc/refman/5.5/en/show-profile.html
默认情况下,参数处于关闭状态,并保存最近15次的运行结果
分析步骤
是否支持,看看当前的mysql版本是否支持
1
2Show variables like 'profiling';
默认是关闭,使用前需要开启开启功能,默认是关闭,使用前需要开启
1
2
3show variables like 'profiling';
set profiling=1;运行SQL
1
2select * from emp group by id%10 limit 150000;
select * from emp group by id%20 order by 5查看结果,show profiles;
诊断SQL,show profile cpu,block io for query n (n为上一步前面的问题SQL数字号码);
type:
| ALL –显示所有的开销信息
| BLOCK IO –显示块IO相关开销
| CONTEXT SWITCHES –上下文切换相关开销
| CPU –显示CPU相关开销信息
| IPC –显示发送和接收相关开销信息
| MEMORY –显示内存相关开销信息
| PAGE FAULTS –显示页面错误相关开销信息
| SOURCE –显示和Source_function,Source_file,Source_line相关的开销信息
| SWAPS –显示交换次数相关开销的信息日常开发需要注意的结论
converting HEAP to MyISAM 查询结果太大,内存都不够用了往磁盘上搬了。
Creating tmp table 创建临时表
1
2
31 select * from emp group by id%20 limit 120000;
2 select * from emp group by id%20 order by 5 拷贝数据到临时表
用完再删除
Copying to tmp table on disk 把内存中临时表复制到磁盘,危险!!!
locked
全局查询日志
配置启用
1 | 在mysql的my.cnf中,设置如下: |
编码启用
1 | 命令 |
永远不要在生产环境开启这个功能。
七、MySql锁机制
概述
定义
锁是计算机协调多个进程或线程并发访问某一资源的机制。
在数据库中,除传统的计算资源(如CPU、RAM、I/O等)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。
生活购物
打个比方,我们到淘宝上买一件商品,商品只有一件库存,这个时候如果还有另一个人买,那么如何解决是你买到还是另一个人买到的问题?
这里肯定要用到事务,我们先从库存表中取出物品数量,然后插入订单,付款后插入付款表信息,然后更新商品数量。在这个过程中,使用锁可以对有限的资源进行保护,解决隔离和并发的矛盾。
锁的分类
从对数据操作的类型(读\写)分
- 读锁(共享锁):针对同一份数据,多个读操作可以同时进行而不会互相影响。
- 写锁(排它锁):当前写操作没有完成前,它会阻断其他写锁和读锁。
从对数据操作的粒度分
- 表锁
- 行锁
三锁
表锁(偏读)
特点
偏向MyISAM存储引擎,开销小,加锁快;无死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
案例分析
建表SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
【表级锁分析--建表SQL】
create table mylock(
id int not null primary key auto_increment,
name varchar(20)
)engine myisam;
insert into mylock(name) values('a');
insert into mylock(name) values('b');
insert into mylock(name) values('c');
insert into mylock(name) values('d');
insert into mylock(name) values('e');
select * from mylock;
【手动增加表锁】
lock table 表名字1 read(write),表名字2 read(write),其它;
【查看表上加过的锁】
show open tables;
【释放表锁】
unlock tables;加读锁
1
2
3我们为mylock表加read锁(读阻塞写例子)
session_1session_2获得表mylock的READ锁定连接终端当前session可以查询该表记录
其他session也可以查询该表的记录 当前session不能查询其它没有锁定的表 其他session可以查询或者更新未锁定的表 当前session中插入或者更新锁定的表都会提示错误: 其他session插入或者更新锁定表会一直等待获得锁: 释放锁 Session2获得锁,插入操作完成:加写锁
1
2mylockwrite(MyISAM)
session_1session_2获得表mylock的WRITE锁定待Session1开启写锁后,session2再连接终端当前session对锁定表的查询+更新+插入操作都可以执行: 其他session对锁定表的查询被阻塞,需要等待锁被释放: 在锁表前,如果session2有数据缓存,锁表以后,在锁住的表不发生改变的情况下session2可以读出缓存数据,一旦数据发生改变,缓存将失效,操作将被阻塞住。释放锁 Session2获得锁,查询返回:
结论
MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行增删改操作前,会自动给涉及的表加写锁。
MySQL的表级锁有两种模式:
表共享读锁(Table Read Lock)
表独占写锁(Table Write Lock)锁类型他人可读他人可写读锁是否写锁否否
结论:
结合上表,所以对MyISAM表进行操作,会有以下情况:
1、对MyISAM表的读操作(加读锁),不会阻塞其他进程对同一表的读请求,但会阻塞对同一表的写请求。只有当读锁释放后,才会执行其它进程的写操作。
2、对MyISAM表的写操作(加写锁),会阻塞其他进程对同一表的读和写操作,只有当写锁释放后,才会执行其它进程的读写操作。
简而言之,就是读锁会阻塞写,但是不会堵塞读。而写锁则会把读和写都堵塞(对于其它session)
行锁(偏写)
特点
- 偏向InnoDB存储引擎,开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
- InnoDB与MyISAM的最大不同有两点:一是支持事务(TRANSACTION);二是采用了行级锁
行锁支持事务
事务(Transaction)及其ACID属性
事务是由一组SQL语句组成的逻辑处理单元,事务具有以下4个属性,通常简称为事务的ACID属性。
- 原子性(Atomicity):事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。
- 一致性(Consistent):在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性;事务结束时,所有的内部数据结构(如B树索引或双向链表)也都必须是正确的。
- 隔离性(Isolation):数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然。
- 持久性(Durable):事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。
并发事务处理带来的问题
更新丢失(Lost Update)
当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题--最后的更新覆盖了由其他事务所做的更新。
例如,两个程序员修改同一java文件。每程序员独立地更改其副本,然后保存更改后的副本,这样就覆盖了原始文档。最后保存其更改副本的编辑人员覆盖前一个程序员所做的更改。
如果在一个程序员完成并提交事务之前,另一个程序员不能访问同一文件,则可避免此问题。
脏读(Dirty Reads)
一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”数据,并据此做进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象地叫做”脏读”。
一句话:事务A读取到了事务B已修改但尚未提交的的数据,还在这个数据基础上做了操作。此时,如果B事务回滚,A读取的数据无效,不符合一致性要求。
不可重复读(Non-Repeatable Reads)
在一个事务内,多次读同一个数据。在这个事务还没有结束时,另一个事务也访问该同一数据。那么,在第一个事务的两次读数据之间。由于第二个事务的修改,那么第一个事务读到的数据可能不一样,这样就发生了在一个事务内两次读到的数据是不一样的,因此称为不可重复读,即原始读取不可重复。
一句话:一个事务范围内两个相同的查询却返回了不同数据。
幻读(Phantom Reads)
一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”。
一句话:事务A 读取到了事务B提交的新增数据,不符合隔离性。
事务隔离级别
“脏读”、“不可重复读”和“幻读”,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决。
数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上 “串行化”进行,这显然与“并发”是矛盾的。同时,不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用对“不可重复读”和“幻读”并不敏感,可能更关心数据并发访问的能力。
常看当前数据库的事务隔离级别:show variables like ‘tx_isolation’;
案例分析
建表SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17create table test_innodb_lock (a int(11),b varchar(16))engine=innodb;
insert into test_innodb_lock values(1,'b2');
insert into test_innodb_lock values(3,'3');
insert into test_innodb_lock values(4,'4000');
insert into test_innodb_lock values(5,'5000');
insert into test_innodb_lock values(6,'6000');
insert into test_innodb_lock values(7,'7000');
insert into test_innodb_lock values(8,'8000');
insert into test_innodb_lock values(9,'9000');
insert into test_innodb_lock values(1,'b1');
create index test_innodb_a_ind on test_innodb_lock(a);
create index test_innodb_lock_b_ind on test_innodb_lock(b);
select * from test_innodb_lock;行锁定基本演示
Session_1Session_2更新但是不提交,没有手写commit; Session_2被阻塞,只能等待提交更新解除阻塞,更新正常进行commit命令执行下面试试1号会话更新a =1 下面试试2号会话更新a =9
无索引行锁升级为表锁
Session_1Session_2正常情况,各自锁定各自的行,互相不影响,一个2000另一个3000由于在column字段b上面建了索引,如果没有正常使用,会导致行锁变表锁比如没加单引号导致索引失效,行锁变表锁被阻塞,等待。只到Session_1提交后才阻塞解除,完成更新
Select也可以加锁
读锁:select ..lock in share mode
共享锁(Share Lock)
共享锁又称读锁,是读取操作创建的锁。其他用户可以并发读取数据,但任何事务都不能对数据进行修改(获取数据上的排他锁),直到已释放所有共享锁。
如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁。获准共享锁的事务只能读数据,不能修改数据。用法
SELECT … LOCK IN SHARE MODE;在查询语句后面增加 LOCK IN SHARE MODE ,Mysql会对查询结果中的每行都加共享锁,当没有其他线程对查询结果集中的任何一行使用排他锁时,可以成功申请共享锁,否则会被阻塞。其他线程也可以读取使用了共享锁的表(行?),而且这些线程读取的是同一个版本的数据。
写锁:select… for update
排他锁(eXclusive Lock)
共享锁又称写锁,如果事务T对数据A加上排他锁后,则其他事务不能再对A加任任何类型的封锁。获准排他锁的事务既能读数据,又能修改数据。
在查询语句后面增加 FOR UPDATE ,Mysql会对查询结果中的每行都加排他锁,当没有其他线程对查询结果集中的任何一行使用排他锁时,可以成功申请排他锁,否则会被阻塞。
间隙锁危害
间隙锁带来的插入问题
Session_1Session_2阻塞产生,暂时不能插入commit;阻塞解除,完成插入【什么是间隙锁】
当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”,
InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(GAP Lock)。【危害】
因为Query执行过程中通过过范围查找的话,他会锁定整个范围内所有的索引键值,即使这个键值并不存在。
间隙锁有一个比较致命的弱点,就是当锁定一个范围键值之后,即使某些不存在的键值也会被无辜的锁定,而造成在锁定的时候无法插入锁定键值范围内的任何数据。在某些场景下这可能会对性能造成很大的危害
案列结论
Innodb存储引擎由于实现了行级锁定,虽然在锁定机制的实现方面所带来的性能损耗可能比表级锁定会要更高一些,但是在整体并发处理能力方面要远远优于MyISAM的表级锁定的。当系统并发量较高的时候,Innodb的整体性能和MyISAM相比就会有比较明显的优势了。
但是,Innodb的行级锁定同样也有其脆弱的一面,当我们使用不当的时候,可能会让Innodb的整体性能表现不仅不能比MyISAM高,甚至可能会更差。
行锁分析
【如何分析行锁定】
通过检查InnoDB_row_lock状态变量来分析系统上的行锁的争夺情况
mysql>show status like ‘innodb_row_lock%’;
对各个状态量的说明如下:
Innodb_row_lock_current_waits:当前正在等待锁定的数量;
Innodb_row_lock_time:从系统启动到现在锁定总时间长度;
Innodb_row_lock_time_avg:每次等待所花平均时间;
Innodb_row_lock_time_max:从系统启动到现在等待最常的一次所花的时间;
Innodb_row_lock_waits:系统启动后到现在总共等待的次数;
对于这5个状态变量,比较重要的主要是
Innodb_row_lock_time_avg(等待平均时长),
Innodb_row_lock_waits(等待总次数)
Innodb_row_lock_time(等待总时长)这三项。
尤其是当等待次数很高,而且每次等待时长也不小的时候,我们就需要分析系统中为什么会有如此多的等待,然后根据分析结果着手指定优化计划。
最后可以通过
SELECT * FROM information_schema.INNODB_TRX\G;
来查询正在被锁阻塞的sql语句。
优化建议
- 尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁。
- 尽可能较少检索条件,避免间隙锁
- 尽量控制事务大小,减少锁定资源量和时间长度
- 锁住某行后,尽量不要去调别的行或表,赶紧处理被锁住的行然后释放掉锁。
- 涉及相同表的事务,对于调用表的顺序尽量保持一致。
- 在业务环境允许的情况下,尽可能低级别事务隔离
页锁
开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。
八、主从复制
复制的基本原理
(1) slave 会从 master 读取 binlog 来进行数据同步
(2) 三步骤+原理图
MySQL 复制过程分成三步:
- master 将改变记录到二进制日志(binary log)。 这些记录过程叫做二进制日志事件, binary log events;
- slave 将 master 的 binary log events 拷贝到它的中继日志(relay log);
- slave 重做中继日志中的事件, 将改变应用到自己的数据库中。 MySQL 复制是异步的且串行化的
复制的基本原则
(1) 每个 slave 只有一个 master
(2) 每个 slave 只能有一个唯一的服务器 ID
(3) 每个 master 可以有多个 salve
复制的最大问题
因为发生多次 IO, 存在延时问题
一主一从常见配置
(1) mysql 版本一致且后台以服务运行
(2) 主从都配置在[mysqld]结点下, 都是小写
主机修改 my.ini 配置文件
1 | 主服务器唯一 ID |
mysql 主从复制起始时, 从机不继承主机数据
(3) logbin 格式
binlog_format=STATEMENT(默认)
binlog_format=ROW
binlog_format=MIXED
(4) 从机配置文件修改 my.cnf 的[mysqld]栏位下
1 | #从机服务 id |
(5) 因修改过配置文件, 请主机+从机都重启后台 mysql 服务
(6) 主机从机都关闭防火墙、 安全工具(腾讯管家等)
(7) 在 Windows 主机上建立帐户并授权 slave
1 | #创建用户, 并授权 |
(8) 查询 master 的状态, 并记录下 File 和 Position 的值
1 | #查询 master 的状态 |
执行完此步骤后不要再操作主服务器 MYSQL, 防止主服务器状态值变化
(9) 在 Linux 从机上配置需要复制的主机
1 | #查询 master 的状态 |
(10) 启动从服务器复制功能
1 | start slave; |
下面两个参数都是 Yes, 则说明主从配置成功!
1 | Slave_IO_Running: Yes |
(11) 主机新建库、 新建表、 insert 记录, 从机复制
(12) 如何停止从服务复制功能
1 | stop slave; |