目录
[TOC]
Mysql基础
数据库相关概念
数据库的好处
- 持久化数据到本地
- 可以实现结构化查询,方便管理
数据库常见名词解释
- DB:数据库,保存一组有组织的数据的容器
- DBMS:数据库管理系统,又称为数据库软件(产品),用于管理DB中的数据
- SQL:结构化查询语言,用于和DBMS通信的语言,不是某个数据库软件特有的,而是几乎所有的主流数据库软件通用的语言
数据库存储数据的特点
- 将数据放到表中,表再放到库中
- 一个数据库中可以有多个表,每个表都有一个的名字,用来标识自己。表名具有唯一性。
- 表具有一些特性,这些特性定义了数据在表中如何存储,类似java中 “类”的设计。
- 表由列组成,我们也称为字段。所有表都是由一个或多个列组成的,每一列类似java 中的”属性”
- 表中的数据是按行存储的,每一行类似于java中的“对象”。
常见数据库
mysql、oracle、db2、sqlserver
MySQL产品的介绍和安装
MySQL背景
前身属于瑞典的一家公司,MySQL AB
08年被sun公司收购
09年sun被oracle收购
MySQL的优点
1、开源、免费、成本低
2、性能高、移植性也好
3、体积小,便于安装
Windows上使用
安装
如果之前没有安装的mysql,则可以直接在官网上下载,使用exe安装。建议下载版本mysql5.5
MySQL数据库管理系统的卸载
如果以前安装过mysql,虽然卸载了,但是安装时出现问题,尝试按下面步骤
- 卸载程序
- 删除安装目录
- 删除C:/ProgramData目录下mysql文件夹
- 卸载服务
- 删除注册表中以下文件夹,如果没有相关的注册表信息可以直接忽略
HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\Eventlog\Application\MySQL文件夹;HKEY_LOCAL_MACHINE\SYSTEM\ControlSet002\Services\Eventlog\Application\MySQL文件夹。HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Eventlog\Application\MySQL的文件夹。 - 卸载服务
- win7系统,以管理员方式,执行命令 sc delete 服务名
- win10系统,删除注册表中 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services 目录下对应服务名文件夹
- 重启电脑
- 删除注册表中以下文件夹,如果没有相关的注册表信息可以直接忽略
MySQL服务的启动和停止
- 计算机——右击管理——服务
- 通过管理员身份运行
net start 服务名(启动服务)
net stop 服务名(停止服务)
登录和退出
- 通过mysql自带的客户端
只限于root用户
- 通过windows自带的客户端
1 | 登录:参数后面没有空格 |
自定义MySQL的配置
修改MySQL安装目录下的my.ini文件,并重启服务
常见的配置有字符集
Linux上使用
以Centos7举例
安装
检查是否安装过mysql
1 | rpm -qa|grep mysql |
默认 Linux(CentOS7) 在安装的时候, 自带了 mariadb(mysql 完全开源版本)相关的组件。
如果系统中存在软件,强制卸载
1 | rpm -e --nodeps mysql-libs |
更改/tmp文件夹权限
mysql安装时,会通过mysql用户在/tmp目录下新建一个tmp_db文件,所以需要给/tmp文件夹赋予更大的权限
1 | chmod -R 777 /tmp |
下载rpm安装包到/opt目录下
建议下载版本mysql5.5,注意linux是32位还是64位
在/opt目录下执行安装
1 | 客户端和服务端 |
检查是否安装成功
1 | mysqladmin --version |
设置mysql密码
1 | /user/bin/mysqladmin –u root password 123456 |
查看安装目录
1 | ps -ef|grep mysql |

启动和关闭服务
1 | 查看服务状态 |
登录和退出
1 | 登录 |
自定义Mysql配置
在/usr/share/mysql/ 中找到 my-huge.cnf 的配置文件, 拷贝到 /etc/ 并命名为 my.cnf ,mysql优先选择/etc/下的配置文件。 添加以下内容后再重启服务。
1 | [client] |
注意: 已经创建的数据库的设定不会发生变化, 参数修改只对新建的数据库有效!
修改已创建库、 表字符集
修改数据库的字符集
1 | alter database mydb character set 'utf8'; |
修改数据表的字符集
1 | alter table mytbl convert to character set 'utf8'; |
修改已经乱码数据
无论是修改 mysql 配置文件或是修改库、 表字符集, 都无法改变已经变成乱码的数据。
只能删除数据重新插入或更新数据才可以完全解决
MySQL的常见命令
1.查看当前所有的数据库
show databases;
mysql:保存用户信息(身份信息,自定义函数、存储过程)
information_schema:保存元数据信息
test:测试信息
performance_schema:监控mysql内部运行情况
2.打开指定的库
use 库名
3.查看当前库的所有表
show tables;
4.查看其它库的所有表
show tables from 库名;
5.创建表
create table 表名(
列名 列类型,
列名 列类型,
。。。
);
6.查看表结构
desc 表名;
7.查看服务器的版本
方式一:登录到mysql服务端
select version();
方式二:没有登录到mysql服务端
mysql --version
或
mysql --V
MySQL的语法规范
- 不区分大小写,但建议关键字大写,表名、列名小写
- 每条命令最好用分号结尾
- 每条命令根据需要,可以进行缩进 或换行
- 注释
- 单行注释:#注释文字
- 单行注释:– 注释文字
- 多行注释:/* 注释文字 */
SQL的语言分类
DQL(Data Query Language):数据查询语言
select
DML(Data Manipulate Language):数据操作语言
insert 、update、delete
DDL(Data Define Languge):数据定义语言
create、drop、alter
TCL(Transaction Control Language):事务控制语言
commit、rollback
SQL的常见命令
show databases; 查看所有的数据库
use 库名; 打开指定 的库
show tables ; 显示库中的所有表
show tables from 库名;显示指定库中的所有表
create table 表名(
字段名 字段类型,
字段名 字段类型
); 创建表
desc 表名; 查看指定表的结构
select * from 表名;显示表中的所有数据
DQL语言的学习
Database Query Language
进阶1:基础查询
语法
- SELECT 要查询的东西 FROM 表名;
- 类似于Java中 :System.out.println(要打印的东西);
特点
- 通过select查询完的结果,是一个虚拟的表格,不是真实存在
- 要查询的东西 可以是常量值、可以是表达式、可以是字段、可以是函数
==建议再编写sql脚本时,在最前面先写上切换库命令 USE myDataBase==
示例
1 | 1、查询单个字段 |
进阶2:条件查询
条件查询:根据条件过滤原始表的数据,查询到想要的数据
1 | 语法: |
进阶3:排序查询
语法:
select
要查询的东西
from
表
where
条件
order by
排序的字段|表达式|函数|别名 【asc升序|desc降序】
SELECT * FORM Employee order by LENGTH(last_name) DESC;
案例
1 | # 查询员工的姓名、部门号和年薪,按年薪降序,按姓名升序 |
进阶4:常见函数
使用函数时,首先要保证有数据,如果没有数据,函数不会执行了
1 | # 这里查询了一条不能存在的数据,由于数据不存在,所以即使salary为null,最终结果还是null,因为函数就没执行 |
单行函数
1 | 1、字符函数 |
日期格式化取值范围
值 | 含义 | |
---|---|---|
秒 | %S、%s | 两位数字形式的秒( 00,01, …, 59) |
分 | %I、%i | 两位数字形式的分( 00,01, …, 59) |
小时 | %H | 24小时制,两位数形式小时(00,01, …,23) |
%h | 两位数形式小时(00,01, …,12) | |
%k | 24小时制,数形式小时(0,1, …,23) | |
%l(L小写) | 12小时制,数形式小时(0,1, …,12) | |
%T | 24小时制,时间形式(HH:mm:ss) | |
%r | 12小时制,时间形式(hh:mm:ss AM 或 PM) | |
%p | AM上午或PM下午 | |
周 | %W | 一周中每一天的名称(Sunday,Monday, …,Saturday) |
%a | 一周中每一天名称的缩写(Sun,Mon, …,Sat | |
%w | 以数字形式标识周(0=Sunday,1=Monday, …,6=Saturday) | |
%U | 数字表示周数,星期天为周中第一天 | |
%u | 数字表示周数,星期一为周中第一天 | |
天 | %d | 两位数字表示月中天数(01,02, …,31) |
%e | 数字表示月中天数(1,2, …,31) | |
%D | 英文后缀表示月中天数(1st,2nd,3rd …) | |
%j | 以三位数字表示年中天数(001,002, …,366) | |
月 | %M | 英文月名(January,February, …,December) |
%b | 英文缩写月名(Jan,Feb, …,Dec) | |
%m | 两位数字表示月份(01,02, …,12) | |
%c | 数字表示月份(1,2, …,12) | |
年 | %Y | 四位数字表示的年份(2015,2016…) |
%y | 两位数字表示的年份(15,16…) | |
文字输出 | 文字 | 直接输出文字内容 |
多行函数
1 | sum 求和 |
案例
1 | 1、查询公司员工工资的最大值、最小值、平均值、总和 |
进阶5:分组查询
1 | 语法: |
案例
1 | 1、查询每个部门中邮箱包含'a'的员工数 |
进阶6:多表连接查询
连接类型
1 | 内连接 |
一、传统模式下的连接 :等值连接——非等值连接
1 | 1.等值连接的结果 = 多个表的交集 |
案例
1 | # 查询有奖金的每个部门的部门名和部门的领导编号和该部门的最低工资 |
二、sql92语法和sql99语法(使用join关键字实现连接)
1 | sql92: |
三、自连接
案例:查询员工名和直接上级的名称
1 | # sql92 |
四、外连接

1 | # 外连接=内连接+主表中有而从表中没有的记录,一般用于查询除了交集部分的剩余的不匹配的行(A-A∩B) |
案例
1 | # 查询没有员工的部门 |
进阶7:子查询
含义:
一条查询语句中又嵌套了另一条完整的select语句,其中被嵌套的select语句,称为子查询或内查询
在外面的查询语句,称为主查询或外查询
特点:
1 | 1、子查询都放在小括号内 |
in和exists的区别
- 从表小,主表大且又有索引时应该用in
- 主表小,从表大且又有索引时使用exists
1 | select * |
案例
1 | # --------------------标量子查询-------------------- |
进阶8:分页查询
应用场景:
实际的web项目中需要根据用户的需求提交对应的分页查询的sql语句
语法:
select 字段|表达式,...
from 表
【where 条件】
【group by 分组字段】
【having 条件】
【order by 排序的字段】
limit 【起始的条目索引,】条目数;
条目数=-1表示遍历到结束
特点:
1 | 1.起始条目索引从0开始 |
==mysql 中,limit 后面不能带运算符,只能是常量==
案例
1 | # 一、查询每个专业的学生人数 |
进阶9:联合查询
引入:
union 联合、合并
语法:
select 字段|常量|表达式|函数 【from 表】 【where 条件】 union 【all】
select 字段|常量|表达式|函数 【from 表】 【where 条件】 union 【all】
select 字段|常量|表达式|函数 【from 表】 【where 条件】 union 【all】
.....
select 字段|常量|表达式|函数 【from 表】 【where 条件】
特点:
1、多条查询语句的查询的列数必须是一致的
2、多条查询语句的查询的列的类型几乎相同
3、union代表去重,union all代表不去重
4、最终结果表的字段名是第一条查询的字段名
案例
1 | # 查询部门编号>90或邮箱包含a的员工信息 |
单引号和双引号的区别
DML语言
Database Manipulation Language
插入
语法:
1 | 方式一: |
特点:
1、字段类型和值类型一致或兼容,而且一一对应
2、可以为空的字段,可以不用插入值,或用null填充
3、不可以为空的字段,必须插入值
4、字段个数和值的个数必须一致
5、字段可以省略,但默认所有字段,并且顺序和表中的存储顺序一致
修改
修改单表语法:
update 表名 set 列名=新值,列名=新值
【where 条件】
# 修改满足条件的数据的字段
修改多表语法:
update 表1 别名1
join|left join|right join|cross 表2 别名2
on 连接条件
set 字段=新值,字段=新值
where 筛选条件;
# 修改张无忌的女朋友的手机号为114
update boys bo
inner join beauty be on bo.id=be.boyfriend_id
set be.phone='114'
where bo.boyName='张无忌'
删除
方式1:delete语句:清空表或数据
1 | 单表的删除: ★ |
方式2:truncate语句:清空表
truncate table 表名
两种方式的区别【面试题】
#1.truncate不能加where条件,而delete可以加where条件
#2.truncate的效率高一丢丢
#3.truncate 删除带自增长的列的表后,如果再插入数据,数据从1开始
# delete 删除带自增长列的表后,如果再插入数据,数据从上一次的断点处开始
#4.truncate删除没有返回值
# delete删除会返回数据的行数
#4.truncate删除不能回滚,delete删除可以回滚
注意
1 | delete from Person |
执行这条语句时会报错:You can’t specify target table ‘Person’ for update in FROM clause
这是因为==MySQL不允许同时查询和删除一张表==(MSSQL和Oracle不会出现此问题),我们可以通过子查询的方式包装一下即可避免这个报错
1 | delete from Person |
DDL语言
Database Definition Language
定义数据库和表
库的定义
1 | 一、创建库 |
表的定义
1 | #1.创建表 |
数据类型
1 | 整型: |
常见约束
六种约束类型
1 | NOT NULL:非空,该字段的值必填 |
可以分为:
- 列级约束:除了外键的所有约束
- 表级约束:除了非空和默认的所有约束,可以给约束命名(主键除外,主键命名固定为PRIMARY)
==唯一,主键,外键都是键,创建后会自动添加索引==
注意
主键与唯一键的异同
是否唯一 | 是否允许为空 | 表中键的数量 | 是否允许组合键 | |
---|---|---|---|---|
主键 | 是 | 否 | 至多1个 | 是 |
唯一键 | 是 | 是 | 没有限制 | 是 |
外键
用于限制两个表的关系,从表的字段值引用了主表的某字段值
外键列和主表的被引用列要求类型一致,意义一样,名称无要求
主表的被引用列要求是一个key(一般就是主键)
插入删除时存在规则
插入数据,先插入主表
删除数据,先删除从表
可以通过以下两种方式来删除主表的记录
级联删除ALTER TABLE stuinfo ADD CONSTRAINT fk_stu_major FOREIGN KEY(majorid) REFERENCES major(id) ON DELETE CASCADE;
级联置空
ALTER TABLE stuinfo ADD CONSTRAINT fk_stu_major FOREIGN KEY(majorid) REFERENCES major(id) ON DELETE SET NULL;
定义约束
创建表时添加约束
1 | 推荐写法 |
修改表时添加或删除约束
1 | 1、非空 |
自增长列
- 不用手动插入值,可以自动提供序列值,默认从1开始,步长为1
auto_increment_increment步长 auto_increment_offset起始值(mysql中不能修改) 如果要更改起始值:第一次插入时,手动插入值 如果要更改步长:更改系统变量 set auto_increment_increment=值;
- 一个表至多有一个自增长列
- 自增长列只能支持数值型
- ==自增长列必须为一个key==
1 | 一、创建表时设置自增长列 |
DTL语言
Database Transaction Language
含义
通过一组逻辑操作单元(一组DML-sql语句),将数据从一种状态切换到另外一种状态
特点(ACID)
- 原子性(Atomicity):要么都执行,要么都回滚
- 一致性(Consistency):保证数据的状态操作前和操作后保持一致(保证数据准确)
- 隔离性(Isolation):多个事务同时操作相同数据库的同一个数据时,一个事务的执行不受另外一个事务的干扰
- 持久性(Durability):一个事务一旦提交,则数据将持久化到本地,除非其他事务对其进行修改
相关步骤:
1、开启事务
2、编写事务的一组逻辑操作单元(多条sql语句)
3、提交事务或回滚事务
事务的分类
隐式事务,没有明显的开启和结束事务的标志
insert、update、delete语句本身就是一个事务
显式事务,具有明显的开启和结束事务的标志
1、禁用自动提交事务的功能,开启事务
set autocommit=0;# 只针对当前事务
start transaction;# 也可以不写
2、编写事务的一组逻辑操作单元(select,insert,update,delete)
DDL并不是事务
3、提交事务或回滚事务(回滚操作不能单纯在mysql上使用,必须结合实际应用,如jdbc)
commit;
rollback;
使用到的关键字
set autocommit=0;
start transaction;
commit;
rollback;
savepoint 断点
commit to 断点
rollback to 断点
事务的隔离级别
事务并发问题如何发生?
当多个事务同时操作同一个数据库的相同数据时
事务的并发问题有哪些?
丢失更新:多个事务对相同数据操作时,最后的更新覆盖了由其他事务所做的更新
(事务1开启事务,事务2在开启并提交事务,事务1回滚,导致事务2的提交被覆盖;
事务1开启事务,事务2在开启并提交事务,事务1提交,导致事务2的提交被覆盖;)
脏读:事务1读取到了事务2已修改但未提交的数据。(如果事务2回滚,则事务1读取的数据就是脏数据)
不可重复读:相同事务,读取结果(数据本身)不一样。(事务1读取数据并未结束,事务2更新数据并提交,事务1再次读取数据,两次读取的数据就不一样了。)
幻读:相同事务,读取结果(数据条数)不一样。(事务1读取数据并未结束,事务2插入或删除数据并提交,事务1再次读取数据,两次读取的数据就不一样了。)
如何避免事务的并发问题?
==对于mysql来说,通常采用前三种隔离级别加上相应的并发锁的机制来控制对数据的访问==
事务隔离级别
丢失更新只能通过并发锁来实现
说明 | 脏读 | 不可重复读 | 幻读 | |
---|---|---|---|---|
READ UNCOMMITTED | 允许 | 允许 | 允许 | |
READ COMMITTED | 事务的更新操作结果只有在该事务提交之后,才能对另一个事务可见 | 不允许 | 允许 | 允许 |
REPEATABLE READ | 保证在整个事务的过程中,对同一笔数据的读取结果是相同的 | 不允许 | 不允许 | 允许 |
SERIALIZABLE | 所有事务必须依次执行,性能差 | 不允许 | 不允许 | 不允许 |
- oracle只支持READ COMMITED(默认)、SERIALIZABLE
- mysql支持四种,默认REPEATABLE READ
设置隔离级别:
set session(当前会话)|global(全局) transaction isolation level 隔离级别名;
查看隔离级别:
select @@tx_isolation;
设置保存点
savepoint+rollback搭配使用实现回滚到固定位置
1 | set autocommit=0; |
delete和truncate在事务中区别
- delete支持回滚
- truncate不支持回滚
视图
含义:理解成一张虚拟的表
视图和表的区别
关键字 | 作用 | 占用物理空间 | |
---|---|---|---|
视图 | view | 用于查询 | 占用较小,仅仅保存的是sql逻辑 |
表 | table | 增删改查 | 保存实际数据 |
视图的好处:
- sql语句提高重用性,效率高
- 和表实现了分离,提高了==安全性==
定义视图
视图结构的查看
1 | DESC test_v7; |
视图的创建
1 | 语法: |
视图的修改
1 | #方式一: |
视图的删除
1 | DROP VIEW test_v1,test_v2,test_v3; |
视图的操作
对于视图数据的操作,如果数据变化,会自动同步到原始表中
1 | 1、查看视图的数据 ★ |
某些视图数据不能操作(几乎所有视图都不允许操作)
- 包含以下关键字的sql语句:分组函数、distinct、group by、having、union或者union all
- 常量视图
- Select中包含子查询
- join
- from一个不能更新的视图
- where子句的子查询引用了from子句中的表
==按照规范来讲,视图就是用于查询,所以应该是只读的,对于一些可以更新的视图,应该给视图加上权限,防止对原始表修改==
案例
1 | # 创建视图emp_v2,查询最高工资高于12000的部门信息 |
变量
系统变量
全局变量
作用域:针对于所有会话(连接)有效,但重启后,配置失效
查看所有全局变量
SHOW GLOBAL VARIABLES;
查看满足条件的部分系统变量
SHOW GLOBAL VARIABLES LIKE '%char%';
查看指定的系统变量的值
SELECT @@global.autocommit;
为某个系统变量赋值
SET @@global.autocommit=0;
SET GLOBAL autocommit=0;
会话变量
作用域:针对于当前会话(连接)有效
如果不写SESSION,默认为当前会话
查看所有会话变量
SHOW [SESSION] VARIABLES;
查看满足条件的部分会话变量
SHOW [SESSION] VARIABLES LIKE '%char%';
查看指定的会话变量的值
SELECT @@[session].tx_isolation;
为某个会话变量赋值
SET @@[session].tx_isolation='read-uncommitted';
SET SESSION tx_isolation='read-committed';
自定义变量
用户变量–@变量名
声明并初始化
SET @变量名=值;
SET @变量名:=值;
SELECT @变量名:=值;
赋值
方式一:一般用于赋简单的值
SET @变量名=值;
SET @变量名:=值;
SELECT @变量名:=值;
方式二:一般用于赋表中的字段值(只能是标量子查询)
SELECT 字段名或表达式 INTO @变量名
FROM 表;
用户变量赋值没有固定类型,即该变量类型定义为弱定义
局部变量–变量名
声明并初始化
declare 变量名 类型 【default 值】;
赋值
方式一:一般用于赋简单的值
SET 变量名=值;
SET 变量名:=值;
SELECT 变量名:=值;
方式二:一般用于赋表 中的字段值
SELECT 字段名或表达式 INTO 变量名
FROM 表;
用户变量与局部变量的区别
作用域 | 定义位置 | 语法 | |
---|---|---|---|
用户变量 | 当前会话 | 会话的任何地方 | @变量名,不用指定类型 |
局部变量 | 定义它的BEGIN END中 | BEGIN END的第一句话 | 一般不用加@,需要指定类型 |
案例
1 | # 声明两个变量并赋初值,求和并打印 |
存储过程
定义
一组经过预先编译的sql语句的集合,类似java中的方法
作用
- 提高了sql语句的重用性,减少了开发程序员的压力
- 提高效率
- 减少编译次数(初次编译,之后使用无需重复编译)
- 减少数据库服务器连接次数(将sql语句进行打包,在一次连接中执行)
创建存储过程
语法
create procedure 存储过程名(参数模式 参数名 参数类型,...)
begin
存储过程体
end
参数模式
- in:该参数作为传入参数,调用时需要传参
- out:该参数作为返回参数
- inout:该参数即作为传入参数,又作为返回参数
- ==这些参数就是局部变量,赋值方式参考局部变量赋值方式==
注意
- 存储过程体中只有一条sql语句,则可以省略begin end
- 存储过程体中每条sql语句结果必须加分号
结束标记delimiter
使用delimiter关键字来设置结束标记
1 | 语法: |
调用存储过程
1 | 语法: |
删除存储过程
1 | drop procedure 存储过程名; |
查看存储过程
1 | show create procedure 存储过程名; |
修改存储过程
不能修改
函数
函数和存储过程的区别
关键字 | 调用语法 | 返回值 | 应用场景 | |
---|---|---|---|---|
函数 | function | select | 有且仅有一个 | 查询结果为一个值 |
存储过程 | procedure | call | 0个或多个 | 更新 |
存储过程中可以直接使用函数
创建函数
学过的函数:LENGTH、SUBSTR、CONCAT等
语法:
delimiter $
CREATE FUNCTION 函数名(参数名 参数类型,...) RETURNS 返回类型
BEGIN
函数体,必须包含return语句
END
select 函数名(参数)$
当函数体只有一条语句时,BEGIN END可以省略
调用函数
1 | SELECT 函数名(实参列表) |
查看函数
1 | show create function 函数名; |
删除函数
1 | drop function 函数名; |
==函数和存储过程的定义,实际存储在数据库名为mysql数据库中proc表下==
流程控制结构
分支
分类
- if函数,简单双分支
- case结构,等值判断的多分支
- if elseif结构,区间判断的多分支
if
1 | 语法 |
case
语法
情况一:类似于switch
case 表达式
when 值1 then 结果1或语句1(如果是语句,需要加分号)
when 值2 then 结果2或语句2(如果是语句,需要加分号)
...
else 结果n或语句n(如果是语句,需要加分号)
end 【case】(如果是放在begin end中需要加上case,如果放在select后面不需要)
select (case
when id%2=0 then id-1
when id=(select max(id) from seat) then id
else id+1 end) as id ,student
from seat
order by id;
情况二:类似于多重if
case
when 条件1 then 结果1或语句1(如果是语句,需要加分号)
when 条件2 then 结果2或语句2(如果是语句,需要加分号)
...
else 结果n或语句n(如果是语句,需要加分号)
end 【case】(如果是放在begin end中需要加上case,如果放在select后面不需要)
特点
then 后面结果是值时,可以在任何地方使用
then 后面结果是语句时,只能在begin-end作用范围内使用
if elseif
语法
if 情况1 then 语句1;
elseif 情况2 then 语句2;
...
else 语句n;
end if;
特点:
只能在begin-end作用范围内使用
案例
1 | delimater $ |
循环
循环结构只能在begin-end作用范围内使用
分类
- loop 死循环
- while 类似java中while
- repeat 类似java中do-while
循环控制语句关键字
- iterate 类似java中continue
- leave 类似java中break
loop
1 | 语法 |
while
语法
【标签:】 WHILE 循环条件 DO
循环体
【iterate 标签】
END WHILE 【标签】;
repeat
1 | 语法: |
案例
1 | # 根据传入的参数n,批量插入n次 |
Mysql高级
Mysql逻辑架构图
和其它数据库相比, MySQL 有点与众不同, 它的架构可以在多种不同场景中应用并发挥良好作用。 主要体现在存储引擎的架构上, 插件式的存储引擎架构将查询处理和其它的系统任务以及数据的存储提取相分离。 这种架构可以根据业务的需求和实际需要选择合适的存储引擎
连接层
最上层是一些客户端和连接服务, 包含本地 sock 通信和大多数基于客户端/服务端工具实现的类似于 tcp/ip 的通信。 主要完成一些类似于连接处理、 授权认证、 及相关的安全方案。 在该层上引入了线程池的概念, 为通过认证安全接入的客户端提供线程。 同样在该层上可以实现基于 SSL 的安全链接。 服务器也会为安全接入的每个客户端验证它所具有的操作权限。
服务层
组件 | 功能 |
---|---|
Management Serveices & Utilities | 系统管理和控制工具 |
SQL Interface: | SQL 接口。 接受用户的 SQL 命令, 并且返回用户需要查询的结果。 比如 select from 就是调用 SQL Interface |
Parser | 解析器。 SQL 命令传递到解析器的时候会被解析器进行语法规则验证和解析 |
Optimizer | 查询优化器。 SQL 语句在查询之前会使用查询优化器对查询进行优化, 比如有 where 条件时, 优化器来决定先投影还是先过滤。 |
Cache 和 Buffer | 查询缓存。 如果查询缓存有命中的查询结果, 查询语句就可以直接去查询缓存中取 数据。 这个缓存机制是由一系列小缓存组成的。 比如表缓存, 记录缓存, key 缓存, 权限缓存等 |
引擎层
存储引擎层, 存储引擎真正的负责了 MySQL 中数据的存储和提取, 服务器通过 API 与存储引擎进行通信。 不同的存储引擎具有的功能不同, 这样我们可以根据自己的实际需要进行选取。
MyISAM引擎和InnoDB引擎区别
InnoDB 的数据是按数据页为单位来进行读写的
InnDB索引不只是B+树,InnoDB引擎有一个特殊的功能叫做“自适应哈希索引(adaptive hash index)”。 当InnoDB注意到某些索引值被使用得非常频繁时,它会在内存中基于B-Tree索引之上再创建-一个哈希索引,这样就让B-Tree索引也具有哈希索引的一些优点,比如快速的哈希查找。这是一个完全自动的、内部的行为,用户无法控制或者配置,不过如果有必要,完全可以关闭该功能。参考网址
对比项 | MyISAM | InnoDB |
---|---|---|
外键 | 不支持 | 支持 |
事务 | 不支持 | 支持 |
行表锁 | 表锁, 即使操作一条记录也会锁住整个表, 不适合高并发的操作 | 行锁,操作时只锁某一行, 不对其它行有影响, 适合高并发的操作 |
缓存 | 只缓存索引, 不缓存真实数据 | 不仅缓存索引还要缓存真实数据, 对内存要求较高, 而且内 存大小对性能有决定性的影响 |
关注点 | 读性能 | 并发写、 事务、 资源 |
默认安装 | Y | Y |
默认使用 | N | Y |
自 带 系 统 表 使用 | Y | N |
是否支持MVCC | N | 支持应对高并发事务, MVCC比单纯的加锁更高效;MVCC只在 READ COMMITTED 和 REPEATABLE READ 两个隔离级别下工作;MVCC可以使用 乐观(optimistic)锁 和 悲观(pessimistic)锁来实现 |
1 | # 查看所有数据库引擎 |
存储层
数据存储层, 主要是将数据存储在运行于裸设备的文件系统之上, 并完成与存储引擎的交互。
Mysql查询与执行流程
查询流程
1 | graph LR |
mysql执行顺序
sql编码顺序

sql执行顺序


常见低性能原因及处理
常见原因
- 查询数据过多
- 关联过多的表,即使用太多Join
- 没有利用索引,或没有利用好索引
- 服务器调优及各个参数设置没有设置适当
- top,free,iostat,vmstat查看系统性能状态
- 内存不足,导致产生大量IO操作
- 锁设置不适当
- 线程阻塞
- 死锁
发现解决问题步骤
- 观察,至少跑1天,看看生产的慢SQL情况
- 开启慢查询日志,设置阈值,比如超过5s的就是慢SQL,记录在日志中
- 使用Explain分析
- show profile 查询SQL在Mysql服务器中的执行细节和生命周期情况
- 运维或DBA,进行数据库服务器的参数调优
索引
帮助Mysql高效获取数据的数据结构
一般而言,对于数据库索引都是B树索引。聚集索引、次要索引、复合索引、前缀索引、唯一索引都默认使用B+树索引。
除B树索引外,也有Hash索引
==虽然可以在表上建立很多个索引,但是查询时,只会使用表中的一个索引==
索引的优缺点
优点
- 降低数据库的IO成本,提高数据检索的效率
- 索引列对数据进行排序, 降低数据排序的成本, 降低了CPU的消耗
缺点
- 数据更新时,索引需要重新建立,增加时间开销
- 索引以文件方式存储在磁盘,带来额外空间开销
索引分类
- 单值索引:一个索引只包含一个列
- 复合索引:一个索引包含多个列,符合索引优先与单值索引
- 唯一索引:值必须唯一,允许有空值。自动在添加唯一约束的列上建立
- 主键索引:设置主键后,默认在主键上建立主键索引。innodb中主键索引为聚集索引
普通索引和唯一索引的区别
- 对于普通索引来说,查找到满足条件的第一个记录后,需要查找下一个记录,直到碰到第一个不满足条件的记录;(可能存在跨页的情况,但是影响几乎可以忽而略)
- 对于唯一索引来说,由于索引定义了唯一性,查找到第一个满足条件的记录后,就会停止继续检索。
- 唯一索引的更新就不能使用 change buffer,实际上也只有普通索引才可以使用
唯一索引的劣势
对于唯一索引来说,所有的操作都要先判断这个操作是否违反了唯一性约束。比如:要插入(4,400)这个记录,就要先判断现在表中是否已经存在 k = 4 的记录,而这必须将数据页读入内存才能判断。如果已经读入到内存了,那就直接更新内存会更快,就没有必要使用 change buffer 了。
数据更新过程
当需要一个数据页的时候
- 如果数据页在内存中就直接更新
- 如果这个数据页还没有在内存中的话,在不影响数据一致性的前提下,InnoDB 会将这些更新操作缓存在 change buffer 中,这样就不需要从磁盘中读入这个数据页了。在下次查询需要访问这个数据页的时候,将数据页读入内存,然后执行 change buffer 中与这个页有关的操作。
- 需要说明的是,虽然名字叫作 change buffer,实际上它是可以持久化的数据。也就是说,change buffer 在内存中有拷贝,也会被写入到磁盘上。
- 将 change buffer 中的操作应用到原数据页,得到最新的结果的过程称为:merge。除了访问这个数据页会触发 merge 外,系统有后台线程会定期 merge。在数据库正常关闭(shutdown)的过程中,也会执行 merge 操作。
好处
- 将更新操作先记录在 change buffer,减少读磁盘,语句的执行速度会得到明显的提升。而且,数据读入内存是需要占用 buffer pool 的,所以这种方式还能够避免占用内存,提高内存利用率。
使用条件
- 唯一索引的更新就不能使用 change buffer,实际上也只有普通索引才可以使用
适用场景
因为 merge 的时候是真正进行数据更新的时刻,而 change buffer 的主要目的就是将记录的变更动作缓存下来,所以在一个数据页做 merge 之前,change buffer 记录的变更越多(也就是这个页面上更新的次数越多),收益就越大。
- 因此,对于写多读少的业务来说,页面在写完以后马上被访问到的概率比较小,此时 change buffer 的使用效果最好。这种业务模型常见的就是账单类、日志类的系统。
索引操作
操作 | 命令 |
---|---|
创建 | CREATE [UNIQUE ] INDEX [indexName] ON table_name(column)) |
删除 | DROP INDEX [indexName] ON mytable; |
查看 | SHOW INDEX FROM table_name |
使 用 Alter 命令 | ALTER TABLE tbl_name ADD PRIMARY KEY (column_list) : 该语句添加一个主键, 这意味着索引值必须是唯一 的, 且不能为 NULL。 |
ALTER TABLE tbl_name ADD UNIQUE index_name (column_list) | |
ALTER TABLE tbl_name ADD INDEX index_name (column_list): 添加普通索引, 索引值可出现多次。 | |
ALTER TABLE tbl_name ADD FULLTEXT index_name (column_list):该语句指定了索引为 FULLTEXT , 用于全文索 引。 |
创建索引
1 | # 创建表时添加索引 |
索引结构
BTree索引
数据存储在所有节点
MyISAM引擎使用

B+Tree索引
真实数据值存储在叶子节点
InnoDB引擎使用

B+树相比B树的优势
- 磁盘读写代价更低
- 非叶子节点不存储数据,只有索引,所以一个盘块中能存储的索引更多,B+树的高度减小,IO次数更少
- 查询效率更加稳定
- 每次查询都要经历等于B+树高度的IO次数,时间一致
Hash索引
键值对方式,Memory和NDB引擎支持,Nosql采用此种索引结构
RTree索引
相比BTree,RTree优势在于范围查找。
RTree在mysql中很少使用,仅支持geometry数据类型。支持该数据类型的存储引擎有:MyISAM、InnbDB、BDB、NDB、Archive
full-text索引
全文检索,支持分词技术
这种方式不再使用,目前都是用专门的搜索引擎代替,如ElasticSearch,Solr
1 | CREATE TABLE article ( |
聚集索引和非聚集索引
聚簇索引
聚簇索引中主键索引与行记录是存储在一起。因此聚集索引不仅是索引,它也是一种数据存储方式。
特点
对于聚集索引而言,数据行在磁盘上的排列方式和在索引上排序一致。
聚集索引字段可以不唯一
聚集索引,有且只能有一个
InnoDB 的表必须要有聚集索引:
- 如果表定义了 PK,则 PK 就是聚集索引;
- 如果表没有定义 PK,则第一个非空 unique 列是聚集索引;
- 否则,InnoDB 会创建一个隐藏的 row-id 作为聚集索引;
非聚簇索引
非聚集索引,也就是普通索引,辅助索引。
如果给表中多个字段加上非聚集索引 , 那么就会出现多个独立的索引结构,每个索引(非聚集索引)互相之间不存在关联
聚集索引与非聚集索引的区别
==通过聚集索引可以查到需要查找的数据 而通过非聚集索引可以查到记录对应的主键值 , 再使用主键的值通过聚集索引查找到需要的数据==
通过主键值找真正数据的过程叫做回表

总结
==不管以任何方式查询表, 最终都会利用主键通过聚集索引来定位到数据, 聚集索引(主键)是通往真实数据所在的唯一路径。==
覆盖索引
特殊情况
有一种例外可以不使用聚集索引就能查询出所需要的数据, 这种非主流的方法 称之为「覆盖索引」查询, 也就是平时所说的复合索引或者多字段索引查询。
由于复合索引会将多个字段添加到索引中,那检索一个字段,不仅会得到主键id,还会得到其他字段的值,如果结果就是这些字段,则不需要再使用主键进行检索
1 | # 建立索引 |
索引创建条件
索引适合的情况
- 主键自动建立唯一索引
- 频繁作为查询条件的字段应该创建索引
- 查询中与其它表关联的字段,外键自动建立索引
- 单键/组合索引的选择问题,组合索引性价比更高
- 查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度
- group by 和order by后面的字段,并且组合索引字段顺序应该和排序字段顺序一致
- 查询中统计或者分组字段
不需要创建索引的情况
- 表记录很少
- 经常修改的表
- 数据重复且分布平均的表字段
- 例如:性别、国籍,这些字段重复率很高
- 索引的选择性:索引列中不同值的数目/总列数。索引值越接近1,这个索引的效率就越高
Explain性能分析
使用 EXPLAIN 关键字可以模拟优化器执行 SQL 查询语句, 从而知道 MySQL 是如何处理你的 SQL 语句的。 分析你的查询语句或是表结构的性能瓶颈。
作用
- 表的读取顺序–explain结果字段id
- 数据读取操作的操作类型–explain结果字段select_type
- 哪些索引可以使用–explain结果字段possible_keys
- 哪些索引被实际使用–explain结果字段key
- 表之间的引用–explain结果字段ref
- 每张表有多少行被优化器查询
Explain使用
语法
Explain + SQL语句
返回结果
字段说明
id:select 查询的序列号,包含一组数字,表示查询中执行 select 子句或操作表的顺序。
- id 相同, 执行顺序由上至下 ,t1->t2->t3
id 不同, 如果是子查询, id 的序号会递增, id 值越大优先级越高, 越先被执行,t3->t2->t1
id 如果相同,从上往下顺序执行;id 值越大, 优先级越高, 越先执行,t3->==derived2(由id为2的查询衍生得到的一张虚表)==->t2
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 |
- table:这个数据是基于哪个表
- type:查询的访问类型
- 结果值从最好到最坏依次是:
- system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
- ==一般来说, 得保证查询至少达到 range 级别, 最好能达到 ref。==
type | 含义 |
---|---|
system | 表只有一行记录(等于系统表), 这是 const 类型的特列, 平时不会出现, 这个也可以忽略不计 |
const | 通常情况下,如果将一个主键(唯一键)放置到where后面作为条件查询,mysql优化器就能把这次查询优化转化为一个常量 select * from department where id = 1; |
eq_ref | 主键唯一性索引扫描, 对于每个索引键, 表中只有一条记录与之匹配,常出现在多表连接中,外表只有一条数据的情况。对于单表,优化器直接转化成const select stu.name,sc.grade from student stu,score sc where stu.sid= sc.id;每个学生只有一条成绩信息 |
ref | 查找条件列使用了索引而且不为主键和unique,即索引查询结果不唯一 select * from person department where name=’张三’;叫张三的人可能有很多 |
range | 指的是有范围的索引扫描(对于表上的扫描不是range),相对于index的全索引扫描,它有范围限制,因此要优于index。 一般就是where后面条件在key上使用了between,and以及’>’,’<’外,in和or等方式扫描 |
index | 在索引上进行全表扫描,没有在索引进行过滤 |
all | 直接进行全表扫描 |
possible_keys:查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用。
key:实际使用的索引,如果为NULL,则表示没有索引或索引没使用(索引失效)。只会选择一个
- possible_keys中mysql推测使用的索引和key中实际使用的索引并没有任何关系
- 这里mysql认为是全表扫描,并没有使用key,但是实际上使用了主键索引
key_len:where后面的筛选字段命中复合索引的长度。长度越长,索引利用率越大,效率越高。可以用来检查是否充分利用索引
对于复合索引(deptno,ename),下面分别为全命中和部分命中的情况
计算长度
类型长度int=4B,varchar(20)=20,varchar和char字符串类型,长度需要考虑具体字符集,utf8 要乘3,GBK要乘2,varchar作为动态字符串类型还需要额外添加2B,允许为空的类型需要额外加上1B
所以5=4+1,67=(20*3+2)+5
ref:查询时索引匹配的字段或者常量。当在索引列上进行等值匹配时,也就是type字段是const、eq_ref、ref、ref_or_null、unique_subquery、index-subquery其中之一时,ref列展示的是与索引列作等值匹配的条件,如常数或者某个字段
- 根据sql语句,在emp表上与ename索引列做等值匹配的是”AvDEjl”,即一个常量,所以ref为const
- 在dept表上与deptno索引列做等值匹配的是emp.’deptno’,即emp中的一个字段,所以ref为mytest.emp.deptno,即数据库名.表名.字段名
rows:rows 列显示 MySQL 认为它执行查询时必须检查的行数。 越少越好!
Extra
Extra | 含义 |
---|---|
Using filesort | 排序时,MySQL 中无法利用索引或字段上没有索引,会对数据使用一个外部的索引排序。 这种不是基于已建立索引的外部索引排序操作称为“文件排序”。需要优化 |
Using temporary | MySQL 在对查询结果排序时使用临时表。需要优化 |
Using index | 表示相应的 select 操作中使用了覆盖索引(Covering Index), 避免访问了表的数据行,即查询的数据都在辅助索引中,没有再根据主键id查找实际数据 如果同时出现 using where, 表明索引被用来执行索引键值的查找; 如果没有同时出现 using where, 表明索引只是用来读取数据而非利用索引执行查找。 |
Using where | 表明使用了 where 过滤 |
Using join buffer | 多表连接时使用了连接缓存 |
impossible where | where 子句的值总是 false, 不能用来获取任何元组。 |
select tables optimized away | 在没有 GROUPBY 子句的情况下,对一些多行函数进行优化 |
distinct | 优化distinct操作,找到第一个匹配的元组就停止 |
案例
1 | select d1.name,(select id from t3) d2 |
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
---|---|---|---|---|---|---|---|---|---|
1 | PRIMARY | derived 3 | system 临时表只有一行数据 |
NULL | NULL 临时表上没有索引 |
NULL | NULL | 1 | |
3 | DERIVED | t1 | ALL | NULL | NULL | NULL | NULL 没有索引匹配 |
1 | Using where |
2 | SUBQUERY | t3 | index | NULL | PRIMARY | 4(id为主键) | NULL | 1 | Using index |
4 | UNION | t2 | ALL | NULL | NULL | NULL | NULL | 1 | |
NULL | UNION RESULT | <union1,4> 操作1和4的结果 |
ALL | NULL | NULL | NULL | NULL | NULL |
索引失效原因及解决
建表sql
1 | CREATE TABLE staffs ( |
全值匹配
按照复合索引顺序进行匹配
删除第一个条件
原因:使用复合索引,查询字段与索引字段顺序的不同会导致, 索引无法充分使用, 甚至索引失效。
==解决:应遵从最左前缀原则==
==注意:mysql5.5.62实测,where后面条件是不需要满足最左前缀==
最左前缀原则
查询时,过滤条件应该从索引的最左前列开始并且不跳过索引中的列。
在索引列上操作
在索引列上进行操作后,索引列会失效
==解决:解决不了,尽量少使用吧==
在复合索引上进行范围查找
若有索引则能使用到索引,范围条件右边的索引(复合索引)会失效
==解决:避免将要进行范围查找的索引列添加到复合索引中,或者将其放在复合索引的最后一项中==
范围查找导致索引失效的原因
1 | 举例 |
结论
对于复合索引而言,后一项索引有效的基础是前一项索引值唯一
使用不等于进行过滤
使用 != 和 <> 的字段索引失效(!= 针对数值类型。 <> 针对字符类型)
原因:对于索引而言,就是一些有序的数据结构,而不等于就是排除了指定条件的范围查找,上面说明了范围查找导致索引失效的原因。
==解决:解决不了,尽量少使用吧==
不为空判断
is not null无法使用索引,is null可以使用索引
is not null也相当于使用了不等于
==解决:解决不了,尽量少使用吧==
通配符匹配以%开头
可以看出,如果%在正则表达式最前面,会导致索引失效。
==解决:==
- ==使用reverse反转函数,将索引中字段反转,然后查询时也使用reverse将条件反转,这样就可以将’%s’变成’s%’==
- ==使用覆盖索引。将条件中的字段和要查询的字段联合,建立复合索引==
数据隐形转换
存在索引列的数据类型隐形转换,则用不上索引。比如列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引
条件中有or
使用or连接时,如果其中一个字段没有索引,则不会使用索引
==解决:要想使用or,又想让索引生效,只能将or条件中的每个列都加上索引==
案例
案例1
假设 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 中间断了(mysql5.5.62是可以的) |
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 |
案例2
建表sql
1 | create table test03( |
建索引
1 | create index idx_test03_c1234 on test03(c1,c2,c3,c4); |
查询
1 | 1)explain select * from test03 where c1='a1' and c2='a2' and c3='a3' and c4='a4'; |
查询优化
使用覆盖索引
查询时应该避免使用select *
可以看出,第二条查询Extra字段多了Using index,即覆盖索引
单表查询优化
建表sql
1 | CREATE TABLE IF NOT EXISTS `article` ( |
查询
EXPLAIN SELECT id,author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;
type=ALL,Extra中Using filesort都需要优化
第一步:建立索引
建立三个字段复合索引create index idx_article_ccv on article(category_id,comments,views);
此时查询分析
可以发现虽然使用了复合索引,但是type是range,且Extra中还有Using filesort
==原因:范围查找会导致索引失效。后面的索引只能建立在前面索引列的单条数据基础上==
第二步:修改索引
只建立两个字段的复合索引create index idx_article_cv on article(category_id,views);
,舍弃了范围查找对应的索引
此时查询分析
可以看出type已经是ref,且Extra中filesort消失
方法
不要在要进行范围查询的字段上建立复合索引,不然会导致后面的索引字段失效
多表查询优化
建表sql
1 | CREATE TABLE IF NOT EXISTS `class` ( |
查询
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
两个查询type都是ALL
第一步:尝试给右表建索引
alter table book add index idx_card(card)
此时查询分析
可以看到在book上使用到了索引,总共查询行数21行
第二步:尝试给左表建索引
删除右表索引,然后在左表上建立索引 drop index idx_card on book
create index on class(card)
此时查询分析
可以看到class表上查询type是index,也就是对索引进行全局扫描,总共查询行数和未加索引之前一样,40行
==原因:左连接会对左表进行全局扫描,所以不管有没有索引,都会遍历。==
方法
多表查询时,在从表建立索引
主表为小表,从表尾大表。小表驱动大表
- ```kotlin
for(i in 0…1000){
}主表 for(i in 0...5){ 从表 }
//————
for(i in 0…5){
}主表 for(i in 0...1000){ 从表 }
//上面两种方式虽然执行次数都是5000次,但是对于mysql而言,二者效率是不一样的
外层循环其实就相当于连接join操作,内存循环就是遍历操作
而连接操作往往都是十分耗时的,所以这就是小表驱动大表的原因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
- 优先优化内层循环,即子查询这些
- 多表查询时,可以设置较大的JoinBuffer
### Order By优化
建表
```mysql
CREATE TABLE tblA(
id int primary key not null auto_increment,
age INT,
birth TIMESTAMP NOT NULL,
name varchar(200)
);
INSERT INTO tblA(age,birth,name) VALUES(22,NOW(),'abc');
INSERT INTO tblA(age,birth,name) VALUES(23,NOW(),'bcd');
INSERT INTO tblA(age,birth,name) VALUES(24,NOW(),'def');
CREATE INDEX idx_A_ageBirth ON tblA(age,birth,name);
- ```kotlin
查询


说明:
MySQL在查询时最多只能使用一个索引。因此,如果WHERE条件已经占用了索引,那么在排序中就不使用索引了。
上面查询中
where age > 20
已经使用了索引idx_A_ageBirth
Where和Order by子句组合满足最佳左前缀原则
如果左边在某一列上使用范围查询,则order by就要从该列开始;
如果左边在某一列上使用精确查询,则order by就要从下一列开始;
Order by子句所有列的排序方向(升序或者降序)应该一样
如果order by后面列不在索引列中,使用filesort时有两种排序方式
- 双路排序
- Mysql4.1之前仅支持双路排序,两次IO操作
- 过程:从表中读取行指针和ORDER BY列,对他们进行排序,一次IO;然后根据排好序的ORDER BY列,从表中读取所需要的字段,返回,一次IO。
- 单路排序
- MySQL4.1之后,主要通过比较我们所设定的系统参数 max_length_for_sort_data的大小和Query 语句所取出的字段类型大小总和来判定需要使用哪一种排序算法。如果 max_length_for_sort_data更大,则使用第二种优化后的算法,反之使用第一种算法。
- 过程:一次性取出满足条件行的所有字段,然后在sort buffer中进行排序,直接返回,一次IO。
Group BY优化
- Group By实质是先排序后分组,分组部分没有太大影响,重要的还是排序。所以其实Group By优化和Order By优化类似
- 对于能放在where后面的条件,就不要放在having后面。
limit优化
日志分析
慢查询日志
MySQL的慢查询日志是MySQL提供的一种日志记录,它用来记录在MySQL中响应时间超过阀值的语句,具体指运行时间超过(大于)long_query_time值的SQL,则会被记录到慢查询日志中。long_query_time的默认值为10s
开启慢查询日志
默认情况下,MySQL数据库没有开启慢查询日志,需要我们手动来设置这个参数。
注意:如果不是调优需要的话,一般不建议启动该参数,因为开启慢查询日志会或多或少带来一定的性能影响。
1 | # 查看慢查询日志配置信息 |
模拟查询
1 | select sleep(4); |
然后打开日志文件,就可以看到记录
直接查询慢日志条数
1 | show global status like '%Slow_queries%'; |
慢日志分析工具
mysql提供慢日志分析工具mysqldumpslow

1 | 常用命令 |
批量数据脚本
使用存储过程实现
建表
1 | # 新建库 |
创建函数
可能会报错
1 | “ERROR 1418 (HY000): This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA in its declaration and binary logging is enabled (you *might* want to use the less safe log_bin_trust_function_creators variable)” |
这是由于启动了二进制日志,mysql会根据log_bin_trust_function_creators变量来表示是否可以信任存储函数创建者,不会创建写入二进制日志引起不安全事件的存储函数。默认值为0,用户不得创建或修改存储函数。因此,需要修改该变量
1 | set global log_bin_trust_function_creators=1; |
创建函数
1 | DELIMITER $$ |
创建存储过程
1 | # 从empno=START开始,向emp表插入max_num条数据 |
使用存储过程
1 | # 员工编号100001-600001 |
Show Profile
mysql提供用来分析当前会话中语句执行的资源消耗情况,用于SQL调优的测量。相比于慢查询日志设置阈值,profile可以查看一段时间sql查询情况。
默认处于关闭状态。
使用
1 | # 查看状态 |
查询时参数
parameters | function |
---|---|
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 temp table
创建临时表Copying to tmp table on disk
把内存中临时表复制到磁盘。十分耗时locked
锁
全局查询日志
不要用于生产环境。了解即可,使用上面的profiles可以更好地分析
1 | set global general_log=1; |
查询结果
Mysql锁
锁是计算机协调多个进程或线程并发访问某一资源的机制。
在数据库中,除传统的计算资源(如CPU、RAM、I/O等)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。
数据库锁的分类
按数据操作类型分类
- 读锁(共享锁):针对同一份数据,多个会话的读操作可以同时进行,但不允许写操作
- 写锁(排它锁):当前会话写操作未完成前,不允许其他会话同时进行其他操作
按操作粒度分类
粒度使用越细,锁住的数据越少,并发度越高
- 行锁
- 表锁
表锁
偏向MyISAM存储引擎,开销小,加锁快,无死锁,锁定粒度大,发生锁冲突的概率最高,并发度低。
案例说明
1 | # 建表 |
情况1:会话A上给表A上加读锁
会话A只能在表A上进行读操作,写操作会报错。如果要对其他表操作,必须先释放锁。
会话B可以在表A上进行读操作,写操作会阻塞,当会话A释放表锁,写操作才能执行。
情况2:会话A上给表A上加写锁
会话A可以在表A上进行读写操作。如果要对其他表操作,必须先释放锁。
会话B对于表A的读写操作都阻塞。如果操作成功,可能是mysql缓存原因,修改下sql语句即可。
分析表锁
1 | show status like 'table%'; |

- Table_locks_immediate:立即获取锁的查询次数
- Table_locks_waited:不能获取表锁而发生等待的次数,此值高则说明存在较严重的表级锁争用情况。
MyISAM的读写锁调度是写优先,这也是MyISAM不适合做写为主的表的引擎,因为写锁后,其他线程不能做任何操作,大量的更新会使查询很难得到锁,从而造成长时间阻塞。
行锁
偏向InnoDB存储引擎,开销大,加锁慢,会出现死锁,锁定粒度小,发生锁冲突的概率低,但并发度高。
InnoDB与MyISAM的最大不同:
- 支持事务
- ==采用行锁(写操作时默认加锁)==。 InnoDB行锁是通过索引上的索引项来实现的,这一点MySQL与Oracle不同,后者是通过在数据中对相应数据行加锁来实现的。InnoDB这种行锁实现特点意味者:只有通过索引条件检索数据,InnoDB才会使用行级锁。没有索引或者索引失效,InnoDB将使用表锁!
建表
1 | drop table if exists test_innodb_lock; |
案例1-innodb默认事务级别
1 | # 关闭自动commit |
可以看到
- 左边事务修改但未提交,右边事务并不能获取到左边事务未提交的数据。
- 左边事务commit之后,右边事务还是不能获取左边事务提交的数据
- 因为innodb默认支持的事务级别REPEATABLE READ,避免了脏读和不可重复读

必须要在右边事务commit之后,才能获取左边事务提交的新数据

案例2-行锁
可以看到使用行锁后(写操作时Innodb引擎默认加锁)
- 不同事务对于同一张表同一数据行的操作进行加锁。右边事务必须等待左边事务提交后,才能提交

案例3-行锁失效
可以看到虽然AB两个事务操作的是不同的数据行
- 由于A事务发生了隐式数据转换(字符串没有加引号),导致索引失效,锁的级别从行锁升级为表锁

案例4-间隙锁
当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁,对于键值在条件范围内但不存在的记录,叫作“间隙(GAP)”。
间隙锁是为了解决幻读而产生的。当进行范围查询时,为了不产生幻读,即两次读取数据条数不一致,对范围内所有数据加锁。
对于事务A,where条件匹配数据库中的项应该是a=3,a=4,a=5
但是事务B在插入a=2时,依然会被锁住。
因为间隙锁实际锁住了a∈[2,5],即使a=2并不存在,此时a=2就叫“间隙”


案例5-主动添加行锁
1 | # 添加共享锁 |
案例6-行锁分析
1 | 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:系统启动后到现在总共等待锁的次数。
这个五个状态量中,比较重要的是:
Innodb_row_lock_time、Innodb_row_lock_time_avg和Innodb_row_lock_waits。尤其是等待次数很高,而且每次等待时长不小时,就可以使用profiles进行分析。

总结
innodb使用更细粒度的行锁,更加适合高并发的场景,但是如果使用不当,当时行锁失效,转换表锁,效率可能比MyISAM更低。
优化建议
- 尽可能让所有数据都通过索引来完成,避免无索引行升级为表锁。
- 合理设计索引,尽量缩小锁的范围。
- 尽可能使用较少的检索条件,避免间隙锁。
- 尽量控制事务大小,减少锁定资源量和时间长度。
- 尽可能降低事务隔离级别
页锁
锁的粒度介于表锁和行锁之间,开销和加锁时间介于表锁和行锁之间,并发度一般
会出现死锁
主从复制
基本步骤
slave会从master读取binlog来进行数据同步。主要有以下三个步骤:
- master将改变记录到二进制日志(binary log),这些记录过程叫做二进制日志事件(binary log events)。
- slave开启一个I/O Thread,将master的binary log events拷贝到中继日志(relay log)。
- slave重做中继日志中的事件,将改变应用到自己的数据库中。MySQL的复制是异步且串行化的。
原则
- 每个slave只能有一个master。(一对一)
- 每个slave只能有一个唯一的服务器ID。
- 每个master可以有多个slave。(一对多)
问题
在主从复制过程中,最大的问题就是延时。
一主一从的常见配置
基本要求
- MySQL版本最好一致且后台以服务运行。
- 保证主机与从机互相ping通。
- 主从配置都在配置文件的[mysqld]结点下,都是小写
- 修改配置文件之后,要重启服务
- 主机和从机都要关闭防火墙,其实也可以配置ip规则。
- windows,手动关闭
- linux,使用命令
service iptables stop
主机配置
修改my.ini配置文件(windows)
设置主机服务器id,
server-id=1
(必须)启用二进制文件。(必须)
1
log-bin="D:/MySQL/MySQL Server 5.5/data/mysqlbin"
启动错误日志(optional)
1
log_error ="D:/MySQL/MySQL Server 5.5/data/mysqlerr"
设置根目录、数据目录(optional)
修改数据文件位置会导致启动报错,解决方式就是将原先的data文件夹中文件全部拷贝到当前data目录中
1
2
3
4
5
6
7
8#mysql安装根目录
basedir="D:/MySQL/MySQL Server 5.5/"
#mysql数据文件所在位置
datadir="D:/MySQL/MySQL Server 5.5/data/"
#mysql临时目录
tmpdir ="D:/MySQL/MySQL Server 5.5/"关闭只读(optional)
1
read-only=0
设置不需要复制的数据库(optional)
1
binlog-ignore-db=mysql
设置需要复制的数据库(optional)
1
binlog-do-db=databasename
重启服务,使用管理员方式执行
1
2net stop mysql
net restart mysql关闭防火墙
从机配置
修改my.cnf配置文件(linux)
设置从服务器ID(necessary)
1
2#linux中 存在该项配置,只需要取消注释,同时注释掉server-id=1
server-id=2启用二进制日志(necessary)
1
2# 配置中已经存在,不需要修改
log-bin=mysql-bin关闭防火墙
1
2
3
4# 检查防火墙状态
service iptables status
# 关闭防火墙
service iptables stop
主机建立账户并授权给slave
创建一个有复制权限的用户
1
GRANT REPLICATION SLAVE ON *.* TO 'slaveaccount(用户名)'@'从机数据库ip' IDENTIFIED BY '密码'
再次刷新权限表
1
2# 在windows中mysql命令行中执行
flush privileges;查询主机状态(要保证是最新的)
File告诉从机需要从哪个文件进行复制,Position告诉从机从文件的哪个位置开始复制,在从机上配置时需用到。
执行完此操作后,尽量不要在操作主服务器MySQL,防止主服务器状态变化(File和Position状态变化)。
从机连接主机
从机中配置主机信息(上一步中账户信息和主机状态)
1
2
3
4
5
6# 在mysql命令中执行
CHANGE MASTER TO MASTER_HOST='主机IP',
MASTER_USER='salveaccount',
MASTER_PASSWORD='密码',
MASTER_LOG_FILE='File名字',
MASTER_LOG_POS=Position数字;启动从机复制功能
1
2
3
4#启动复制
start slave;
# 关闭复制
stop slave;查看复制状态
1
show slave status\G;
只有当Slave_IO_Running:Yes和Slave_SQL_Running:Yes,这两个都为Yes的时候,主从复制配置才成功。
测试
在主机建立数据库并插入数据

检查从机是否包含刚刚创建的数据库

MySQL之MVVC简介
一丶什么是MVCC?
MVCC (Multi-Version Concurrency Control) (注:与MVCC相对的,是基于锁的并发控制,Lock-Based Concurrency Control)是一种基于多版本的并发控制协议,只有在InnoDB引擎下存在。MVCC是为了实现事务的隔离性,通过版本号,避免同一数据在不同事务间的竞争,你可以把它当成基于多版本号的一种乐观锁。当然,这种乐观锁只在事务级别未提交锁和已提交锁时才会生效。MVCC最大的好处,相信也是耳熟能详:读不加锁,读写不冲突。在读多写少的OLTP应用中,读写不冲突是非常重要的,极大的增加了系统的并发性能。具体见下面介绍。
二丶MVCC的实现机制
InnoDB在每行数据都增加两个隐藏字段,一个记录创建的版本号,一个记录删除的版本号。
在多版本并发控制中,为了保证数据操作在多线程过程中,保证事务隔离的机制,降低锁竞争的压力,保证较高的并发量。在每开启一个事务时,会生成一个事务的版本号,被操作的数据会生成一条新的数据行(临时),但是在提交前对其他事务是不可见的,对于数据的更新(包括增删改)操作成功,会将这个版本号更新到数据的行中,事务提交成功,将新的版本号更新到此数据行中,这样保证了每个事务操作的数据,都是互不影响的,也不存在锁的问题。
三丶MVCC下的CRUD
SELECT:
当隔离级别是REPEATABLE READ时select操作,InnoDB必须每行数据来保证它符合两个条件:
1、InnoDB必须找到一个行的创建记录版本,它至少要和事务的版本一样老(也即它的版本号不大于事务的版本号)。这保证了不管是事务开始之前,或者事务创建时,或者修改了这行数据的时候,这行数据是存在的。
2、这行数据的删除版本必须是未定义的或者比事务版本要大。这可以保证在事务开始之前这行数据没有被删除。
符合这两个条件的行可能会被当作查询结果而返回。
INSERT:
InnoDB为这个新行记录当前的系统版本号。
DELETE:
InnoDB将当前的系统版本号设置为这一行的删除ID。
UPDATE:
InnoDB会写一个这行数据的新拷贝,这个拷贝的版本为当前的系统版本号。它同时也会将这个版本号写到旧行的删除版本里。
这种额外的记录所带来的结果就是对于大多数查询来说根本就不需要获得一个锁。他们只是简单地以最快的速度来读取数据,确保只选择符合条件的行。这个方案的缺点在于存储引擎必须为每一行存储更多的数据,做更多的检查工作,处理更多的善后操作。
MVCC只工作在REPEATABLE READ和READ COMMITED隔离级别下。READ UNCOMMITED不是MVCC兼容的,因为查询不能找到适合他们事务版本的行版本;它们每次都只能读到最新的版本。SERIABLABLE也不与MVCC兼容,因为读操作会锁定他们返回的每一行数据。
数据库的redo,undo
redo
Redo是物理日志,记录的是数据页的物理变化。而逻辑Redo日志,不是记录页面的实际修改,而是记录修改页面的一类操作
使用redo log原因
- 事务提交后,必须将事务对数据页的修改刷(fsync)到磁盘上,才能保证事务的ACID特性。
- 这个刷盘,是一个随机写,随机写性能较低,如果每次事务提交都刷盘,会极大影响数据库的性能。
架构设计中有两个常见的优化方法:
(1)先写日志(write log first),将随机写优化为顺序写;
(2)将每次写优化为批量写;
第一条就是redo,某一时刻,数据库崩溃,还没来得及将数据页刷盘,数据库重启时,会重做redo log里的内容,以保证已提交事务对数据的影响被刷到磁盘上。
log是为了保证已提交事务的ACID特性,同时能够提高数据库性能的技术。
使用redo log,数据库崩溃,导致数据丢失原因
redo的三层架构
*redo log*****最终落盘的步骤如何?****
首先,事务提交的时候,会写入Log Buffer,这里调用的是MySQL自己的函数WriteRedoLog;
接着,只有当MySQL发起系统调用写文件write时,Log Buffer里的数据,才会写到OS cache。注意,==MySQL系统调用完write之后,就认为文件已经写完,如果不flush,什么时候落盘,是操作系统决定的==;
- 画外音:有时候打日志,明明printf了,tail -f**却看不到,就是这个原因,这个细节在《明明打印到文件了,为啥tail -f看不到》一文里说过,此处不再展开。
最后,由操作系统(当然,MySQL也可以主动flush)将OS cache里的数据,最终fsync到磁盘上;
*操作系统为什么要缓冲数据到*****OS cache*****里,而不直接刷盘呢?*
这里就是将“每次写”优化为“批量写”,以提高操作系统性能。
*数据库为什么要缓冲数据到*****Log Buffer**里,而不是直接**write*****呢?*
这也是“每次写”优化为“批量写”思路的体现,以提高数据库性能。
画外音:**这个优化思路,非常常见,高并发的MQ落盘,高并发的业务数据落盘,都可以使用。
redo log的三层架构,MySQL做了一次批量写优化,OS做了一次批量写优化,确实能极大提升性能,但有什么副作用吗?
画外音:**有优点,必有缺点。
这个副作用,就是可能丢失数据:
(1)事务提交时,将redo log写入Log Buffer,就会认为事务提交成功;
(2)如果写入Log Buffer的数据,write入OS cache之前,数据库崩溃,就会出现数据丢失;
(3)如果写入OS cache的数据,fsync入磁盘之前,操作系统奔溃,也可能出现数据丢失;
画外音:如上文所说,应用程序系统调用完write之后(不可能每次write后都立刻flush,这样写日志很蠢),就认为写成功了,操作系统何时fsync**,应用程序并不知道,如果操作系统崩溃,数据可能丢失。
undo
undo log主要记录的是数据的逻辑变化,为了在发生错误时回滚之前的操作,需要将之前的操作都记录下来,然后在发生错误时才可以回滚。
undo log的作用
undo是一种逻辑日志,有两个作用:
- 用于事务的回滚
- MVCC
关于MVCC(多版本并发控制)的内容这里就不多说了,本文重点关注undo log用于事务的回滚。
undo日志,只将数据库逻辑地恢复到原来的样子,在回滚的时候,它实际上是做的相反的工作,比如一条INSERT ,对应一条 DELETE,对于每个UPDATE,对应一条相反的 UPDATE,将修改前的行放回去。undo日志用于事务的回滚操作进而保障了事务的原子性。
undo log的写入时机
- DML操作修改聚簇索引前,记录undo日志
- 二级索引记录的修改,不记录undo日志
需要注意的是,undo页面的修改,同样需要记录redo日志。
1 | 假设有A、B两个数据,值分别为1,2. |