MySql基础知识

272 阅读32分钟

数据库

通用语法及分类

  • DDL: 数据定义语言,用来定义数据库对象(数据库、表、字段)
  • DML: 数据操作语言,用来对数据库表中的数据进行增删改
  • DQL: 数据查询语言,用来查询数据库中表的记录
  • DCL: 数据控制语言,用来创建数据库用户、控制数据库的控制权限

DDL(数据定义语言)

数据库操作

# 1. 查询所有数据库:
show databases;
# 2. 查询当前所在数据库:
select database();
# 3. 创建数据库:
create database [if not exists] 数据库名 [default charset 字符集] [collate 排序规则];
# 4. 删除数据库:
drop database [if exists] 数据库名;
# 5. 使用数据库:
use 数据库名;

注意事项: UTF8字符集长度为3字节,有些符号占4字节,所以推荐用utf8mb4字符集

表操作

# 1. 查询当前数据库所有表:
show tables;
# 2. 查询表结构:
desc 表名;
# 3. 查询指定表的建表语句:
show create table 表名;
# 4. 创建表
create table 表名(
    字段1 字段1类型 [comment 字段1注释],
    字段2 字段2类型 [comment 字段2注释],
    字段3 字段3类型 [comment 字段3注释],
    ...
    字段n 字段n类型 [comment 字段n注释]
) [comment 表注释]; 
# 5. 添加字段
alter table 表名 add 字段名 类型(长度) [comment 字段注释];
# 6. 修改数据类型
alter table 表名 modify 字段名 新数据类型(长度);
# 7. 修改字段名和字段类型
alter table 表名 change 旧字段名 新字段名 新数据类型(长度) [comment 字段注释]# 8. 删除字段
alter table 表名 drop 字段名;
# 9. 修改表名
alter table rename to 新表名;
# 10. 删除表
drop table [if exists] 表名;
# 11. 删除表,并重新创建该表
truncate table 表名;

MySql数据类型

数值类型

类型大小Signed范围Unsigned范围描述
TINYINT1byte(-128, 127)(0, 1255)小整数值
SMALLINT2 bytes(-32768, 32767)(0, 65535)大整数值
MEDIUMINT3 bytes(-8388608, 8388607)(0, 16777215)大整数值
INT/INTEGER4 bytes(-2147483648, 2147483647)(0, 4294967265)大整数值
BIGINT8 bytes极大整数值
FLOAT4 bytes单精度浮点数
DOUBLE8 bytes双精度浮点数
DECIMAL依赖于M(精度)和D(标度)的值依赖于M(精度)和D(标度)的值小数值(精确定点数)

字符串类型

类型大小描述
CHAR0 - 255 bytes定长字符串(需要指定长度)
VARCHAR0 - 65535 bytes变长字符串(需要指定长度)
TINYBLOB0 -255 bytes不超过255个字符的二进制数据
TINYTEXT0 -255 bytes短文本字符串
BLOB0 - 65535 bytes二进制形式的长文本数据
TEXT0 - 65535 bytes长文本数据
MEDIUMBLOB0 - 16777215 bytes二进制形式的中等长度极大文本数
MEDIUMTEXT0 - 16777215 bytes中等长度文本数据
LONGBLOB0 - 4294967295 bytes二进制形式的极大文本数
LONGTEXT0 - 4294967295 bytes极大文本数据

日期时间类型

类型大小范围格式描述
DATE31000-01-01 至 9999-12-31YYYY-MM-DD日期值
TIME3-838:59:59 至 838:59:59HH:MM:SS时间值或持续时间
YEAR11901 至 2155YYYY年份值
DATETIME81000-01-01 00:00:00 至 9999-12-31 23:59:59YYYY-MM-DD HH:MM:SS混合日期和时间
TIMESTAMP41970-01-01 00:00:01 至 2038-01-19 03:14:07YYYY-MM-DD HH:MM:SS混合日期和时间值,时间戳

DML(数据操作语言)

# 指定字段
insert into 表名 (字段1, 字段2...) values (值1, 值2...), (值1, 值2...);

# 全部字段
insert into 表名 values (值1, 值2...), (值1, 值2...);

# 注意事项
# 字符串和日期类型数据应该包含在引号中
# 插入的数据大小应该在字段的规定范围内

改删

# 修改数据
update 表名 set 字段1=值1, 字段2=值2,... [where 条件];

# 删除数据
delete from 表名 [where 条件];
# 只能删除整行数据,如果删除单个数据可以通过update修改为null

DQL(数据查询语言)

基础查询

# 查询多个字段
select 字段1, 字段2 from 表名;
#查询所有数据
select * from 表名;

# 设置别名 as可以省略
select 字段1 [as 别名1], 字段2 [as 别名2] from 表名;

#去除重复记录
select distinct 字段列表 from 表名;

条件查询

# 语法
select 字符列表 from 表名 where 条件列表;
比较运算符功能
>大于
>=大于等于
<小于
<=小于等于
=等于
<> 或 !=不等于
BETWEEN … AND …在某个范围内(含最小、最大值)
IN(…)在in之后的列表中的值,多选一
LIKE 占位符模糊匹配(_匹配单个字符,%匹配任意个字符)
IS NULL是NULL
逻辑运算符功能
AND 或 &&并且(多个条件同时成立)
OR 或 ||或者(多个条件任意一个成立)
NOT 或 !非,不是
# 例子
# 年龄大于等于18岁的员工
select * from employee where age >= 18# 没有填写身份证号的员工
select * from employee where idcard is null or idcard = '';
# 有身份证号的员工
select * from employee where idcard is not null;
# 年龄在20岁到30岁之间的员工(between 小值 and 大值)
select * from employee where age between 20 and 30;
select * from employee where age >= 20 and age <= 30;
# 年龄等于25或30或35
select * from employee where age = 25 or age = 30 or age = 35;
select * from employee where age in (25, 30, 35);
# 姓名为两个字
select * from employee where name like '__';
# 身份证最后为X
select * from employee where idcard like '%X';

聚合查询

# 语法
select 聚合函数(字段名) from 表名;
# 统计安徽省的有多少人
select count(id) from employee where workaddress='安徽省';
函数功能
count统计数量
max最大值
min最小值
avg平均值
sum求和

分组查询

# 语法
select 字段列表 from 表名 where 条件列表 group by 分组字段名 [having 分组后的过滤条件];
# wherehaving 的区别:
    # 执行时机不同:where是分组之前进行过滤,不满足where条件不参与分组;having是分组后对结果进行过滤
    # 判断条件不同:where不能对聚合函数进行判断,而having可以
# 例子
# 根据性别分组,统计男性和女性数量
select gender count(*) from employee group by gender;
# 年龄小于45,并根据工作地址分组,获取员工数量大于等于3的工作地址
select workaddress count(id) from employee where age < 45 group by workaddress having count(id) >= 3;
# count(id) 也可以起个别名,having过滤时可以通过别名过滤

# 注意事项
# 执行顺序:where > 聚合函数 > having
# 分组之后,查询的字段一般为聚合函数和分组字段,查询其他字段无任何意义

排序查询

# 语法
select 字段列表 from 表名 order by 字段1 排序方式1, 字段2 排序方式2;
# 排序方式
# asc 升序(默认)
# desc 降序

# 例子
# 根据年龄升序排序,年龄相同根据入职时间降序排序
select * from employee order by age, entrydata desc;

分页查询

# 语法
select 字段列表 from 表名 limit 起始索引, 查询记录数;

# 例子
# 查询第二页
select * from employee limit 10, 10;

# 扩展
# 分页查询是数据库的方言,不同数据库有不同实现,MySQL是LIMIT
# 如果查询的是第一页数据,起始索引可以省略,直接简写 LIMIT 10

DQL执行顺序
FROM -> WHERE -> GROUP BY -> SELECT -> ORDER BY -> LIMIT

DCL(数据控制语言)

管理用户

# 查询用户
use mysql;
select * from user;

# 创建用户
create user '用户名'@'主机名' identified by '密码';
# 修改用户密码
alter user '用户名'@'主机名' identified with mysql_native_password by '新密码';
# 删除用户
drop user '用户名'@'主机名';
# 例子
# 创建用户test,只能在当前主机localhost访问
create user 'test'@'localhost' identified by '123456';
# 创建用户test,能在任意主机访问
create user 'test'@'%' identified by '123456';
# 修改密码
alter user 'test'@'localhost' identified with mysql_native_password by '1234';
# 删除用户
drop user 'test'@'localhost';

# 扩展
# 主机名可以使用 % 通配

权限控制

权限说明
ALL, ALL PRIVILEGES所有权限
SELECT查询数据
INSERT插入数据
UPDATE修改数据
DELETE删除数据
ALTER修改表
DROP删除数据库/表/视图
CREATE创建数据库/表
# 查询权限
show grants from '用户名'@'主机名';
# 授予权限
grant 权限列表 on 数据库名.表名 to '用户名'@'主机名';
# 撤销权限
revoke 权限列表 on 数据库名.表名 from '用户名'@'主机名';

# 注意事项
# 多个权限用逗号分隔
# 授权时,数据库名和表名可以用 * 进行通配,代表所有

函数

字符串函数

函数功能
CONCAT(s1, s2, …, sn)字符串拼接,将s1, s2, …, sn拼接成一个字符串
LOWER(str)将字符串全部转为小写
UPPER(str)将字符串全部转为大写
LPAD(str, n, pad)左填充,用字符串pad对str的左边进行填充,达到n个字符串长度
RPAD(str, n, pad)右填充,用字符串pad对str的右边进行填充,达到n个字符串长度
TRIM(str)去掉字符串头部和尾部的空格
SUBSTRING(str, start, len)返回从字符串str从start位置起的len个长度的字符串
REPLACE(column, source, replace)替换字符串
-- 拼接
SELECT CONCAT('Hello', 'World');
-- 小写
SELECT LOWER('Hello');
-- 大写
SELECT UPPER('Hello');
-- 左填充
SELECT LPAD('01', 5, '-');
-- 右填充
SELECT RPAD('01', 5, '-');
-- 去除空格
SELECT TRIM(' Hello World ');
-- 切片(起始索引为1)
SELECT SUBSTRING('Hello World', 1, 5);

数值函数

函数功能
CEIL(x)向上取整
FLOOR(x)向下取整
MOD(x, y)返回x/y的模
RAND()返回0~1内的随机数
ROUND(x, y)求参数x的四舍五入值,保留y位小数

日期函数

函数功能
CURDATE()返回当前日期
CURTIME()返回当前时间
NOW()返回当前日期和时间
YEAR(date)获取指定date的年份
MONTH(date)获取指定date的月份
DAY(date)获取指定date的日期
DATE_ADD(date, INTERVAL expr type)返回一个日期/时间值加上一个时间间隔expr后的时间值
DATEDIFF(date1, date2)返回起始时间date1和结束时间date2之间的天数
# 例子
select date_add(now(), interval 70 year);

流程函数

函数功能
IF(value, t, f)如果value为true,则返回t,否则返回f
IFNULL(value1, value2)如果value1不为空,返回value1,否则返回value2
CASE WHEN [ val1 ] THEN [ res1 ] … ELSE [ default ] END如果val1为true,返回res1,… 否则返回default默认值
CASE [ expr ] WHEN [ val1 ] THEN [ res1 ] … ELSE [ default ] END如果expr的值等于val1,返回res1,… 否则返回default默认值
# 例子
# 查看员工的名字和年龄段,大于30岁记为中年,否则记为青年
select name (case when age > 30 then '中年' else '青年' end) from employee;
# 查看员工的姓名和工作地址,如果是北京市和上海市就记为一线城市,否则记为二线城市
select name (case workaddress when '北京市' then '一线城市' when '上海市' then '一线城市' else '二线城市' end) as '工作地址' from employee;

约束

常用约束

约束描述关键字
非空约束限制该字段的数据不能为nullNOT NULL
唯一约束保证该字段的所有数据都是唯一、不重复的UNIQUE
主键约束主键是一行数据的唯一标识,要求非空且唯一PRIMARY KEY
自动增长用于主键自增,录入数据时可以直接不录主键AUTO_INCREMENT
默认约束保存数据时,如果未指定该字段的值,则采用默认值DEFAULT
检查约束(8.0.1版本后)保证字段值满足某一个条件CHECK
外键约束用来让两张图的数据之间建立连接,保证数据的一致性和完整性FOREIGN KEY
# 例子
create table user(
    id int primary key auto_increment,
    name varchar(10) not null unique,
    age int check(age > 0 and age < 120),
    status char(1) default '1',
    gender char(1)
);

# 增加数据时,如果仅不满足unique条件,会报错,但是会占用一个主键序号,可以理解为加入到表中才能对比

外键约束

# 创建表时添加外键
create table 表名(
    字段名 字段类型 约束 [comment 字段注释],
    ...
    [constraint] 外键名称 foreign key (外键字段名) references 主表(主表列名)
) [comment 表注释];

# 单独添加外键
alter table 表名 add [constraint] 外键名称 foreign key (外键字段名) references 主表名(主表列名)

# 例子
alter table worker add constraint fk_worder_dept_id foreign key (dept_id) references dept(id);

# 删除外键
alter table 表名 drop foreign key 外键名称;

删除/更新行为

行为说明
NO ACTION当在父表中删除/更新对应记录时,首先检查该记录是否有对应外键,如果有则不允许删除/更新(与RESTRICT一致)
RESTRICT当在父表中删除/更新对应记录时,首先检查该记录是否有对应外键,如果有则不允许删除/更新(与NO ACTION一致)
CASCADE当在父表中删除/更新对应记录时,首先检查该记录是否有对应外键,如果有则也删除/更新外键在子表中的记录
SET NULL当在父表中删除/更新对应记录时,首先检查该记录是否有对应外键,如果有则设置子表中该外键值为null(要求该外键允许为null)
SET DEFAULT父表有变更时,子表将外键设为一个默认值(Innodb不支持)
# 修改删除更新行为
alter table 表名 add constraint 外键名称 foreign key 外键字段名 references 主表名(主表列名) on update 行为 on delete 行为;

多表查询

多表关系

  • 一对多(多对一)
  • 多对多
  • 一对一

一对多

案例:部门与员工
关系:一个部门对应多个员工,一个员工对应一个部门
实现:在多的一方(员工)建立外键,指向一的一方的主键

多对多

案例:学生与课程
关系:一个学生可以选多门课程,一门课程也可以供多个学生选修
实现:建立第三张中间表,中间表至少包含两个外键,分别关联两方主键

一对一

案例:用户与用户详情
关系:一对一关系,多用于单表拆分,将一张表的基础字段放在一张表中,其他详情字段放在另一张表中,以提升操作效率
实现:在任意一方加入外键,关联另外一方的主键,并且设置外键为唯一的(UNIQUE)

查询

笛卡尔积:两个集合A集合和B集合的所有组合情况(在多表查询时,需要消除无效的笛卡尔积)

# 合并查询(笛卡尔积,会展示所有组合结果):
select * from employee, dept;

# 消除无效笛卡尔积
select * from employee, dept where employ.dept_id = dept.id;

内连接查询

# 内连接查询的是两张表交集的部分
# 隐式内连接
select 字段列表 from 表名1, 表名2 where 条件列表;
# 显示内连接
select 字段列表 from 表名1 inner join 表名2 on 连接条件;

# 例子
# 查询员工姓名,及关联的部门的名称
# 隐式
select e.name, d.name from employee as e, dept as d where e.dept = d.id;
# 显式
select e.name, d.name from employee as e inner join dept as d on e.dept = d.id;

外连接查询

# 左外连接: 查询左表所有数据,以及两张表交集部分数据
select 字段列表 from1 left outer join2 on 连接条件;
# 右外连接: 查询右表所有数据,以及两张表交集部分数据
select 字段列表 from1 right outer join2 on 连接条件;

# 例子
# 左
select e.*, d.name from employee as e left outer join dept as d on e.dept = d.id;
select d.name, e.* from dept d left outer join emp e on e.dept = d.id; # 这条语句与下面的语句效果一样
# 右
select d.name, e.* from employee as e right outer join dept as d on e.dept = d.id;
# 注意事项
# 左连接可以查询到没有dept的employee,右连接可以查询到没有employee的dept
# 内连接会过滤掉值为空的数据

自连接查询

# 当前表与自身的连接查询,自连接必须使用表别名
select 字段列表 from 表名 别名A join 表名 别名B on 连接条件;
# 自连接查询,可以是内连接查询,也可以是外连接查询

# 例子
# 查询员工及其所属领导的名字
select a.name, b.name from employee a, employee b where a.manager = b.id;
# 没有领导的也查询出来
select a.name, b.name from employee a left join employee b on a.manager = b.id;

联合查询 UNION, UNION ALL

# 把多次查询的结果合并,形成一个新的查询集
# 语法
select 字段列表 from 表A
union [all]
select 字段列表 from 表B;
# 注意事项
# union all 可能会有重复结果 union不会
# 联合查询比使用or效率高,不会使索引失效

子查询

  • 标量子查询(子查询结果为单个值)
  • 列子查询(子查询结果为一列)
  • 行子查询(子查询结果为一行)
  • 表子查询(子查询结果为多行多列)

标量子查询

# 例子
# 查询销售部的所有员工
select * from employee where dept = (select id from dept where name = '销售部');
# 查询xxx入职之后的员工信息
select * from employee where entrydate > (select entrydate from employee where name = 'xxx');

列子查询

操作符描述
IN在指定的集合范围内,多选一
NOT IN不在指定的集合范围内
ANY子查询返回列表中,有任意一个满足即可
SOME与ANY等同,使用SOME的地方都可以使用ANY
ALL子查询返回列表的所有值都必须满足
# 例子
# 查询销售部和市场部的所有员工信息
select * from employee where dept in (select id from dept where name = '销售部' or name = '市场部');
# 查询比财务部所有人工资都高的员工信息
select * from employee where salary > all(select salary from employee where dept = (select id from dept where name = '财务部'));
# 查询比研发部任意一人工资高的员工信息
select * from employee where salary > any (select salary from employee where dept = (select id from dept where name = '研发部'));

行子查询

# 例子
# 查询与xxx的薪资及直属领导相同的员工信息
select * from employee where (salary, manager) = (select salary, manager from employee where name = 'xxx');`

表子查询

# 查询与xxx1,xxx2的职位和薪资相同的员工`
select * from employee where (job, salary) in (select job, salary from employee where name = 'xxx1' or name = 'xxx2');
# 查询入职日期是2006-01-01之后的员工,及其部门信息
select e.*, d.* from (select * from employee where entrydate > '2006-01-01') as e left join dept as d on e.dept = d.id;

事务

事务是一组操作的集合,事务会把所有操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。

# 例子: 银行转账
create table account(
    id int primary key auto_increment comment '主键',
    name varchar(10) comment '姓名',
    money int comment '余额'
) comment '账户表';

insert into account (name, money) values ('张三', 2000), ('李四', 2000);

-- 恢复数据
update account set money = 2000 where name = '张三' or name = '李四';

/* 方式一:设置手动提交,一系列操作执行后观察是否出错,出错则回滚,否则提交 */
select @@autocommit; -- 默认为1,自动提交
set @@autocommit = 0; -- 设置为手动提交

-- 转账操作
-- 1. 查询张三账户余额
select money from account where name = '张三';
-- 2. 将张三账户余额 -1000
update account set money = money - 1000 where name = '张三';
-- 3. 将李四账户余额 +1000
update account set money = money + 1000 where name = '李四';

-- 提交事务
commit;

-- 回滚事务
rollback ;
/* 方式二:开启事务 执行操作*/
start transaction; -- 开启事务

-- 转账操作
-- 1. 查询张三账户余额
select money from account where name = '张三';
-- 2. 将张三账户余额 -1000
update account set money = money - 1000 where name = '张三';
-- 3. 将李四账户余额 +1000
update account set money = money + 1000 where name = '李四';

-- 提交事务
commit;

-- 回滚事务
rollback ;

/* 查看事务隔离级别 */
select @@transaction_isolation;

-- 设置事务隔离级别
set session transaction isolation level read uncommitted;

set session transaction isolation level repeatable read;

四大特性ACID

  • 原子性(Atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败
  • 一致性(Consistency):事务完成时,必须使所有数据都保持一致状态
  • 隔离性(Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行
  • 持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的

并发事务

问题描述
脏读一个事务读到另一个事务还没提交的数据
不可重复读一个事务先后读取同一条记录,但两次读取的数据不同
幻读一个事务按照条件查询数据时,没有对应的数据行,但是在插入数据时,又发现这行数据已经存在

这三个问题的详细演示

隔离级别脏读不可重复读幻读
Read uncommitted
Read committed×
Repeatable Read(默认)××
Serializable×××
  • √ 表示在当前隔离级别下该问题会出现
  • Repeatable Read可以理解为快照读
  • Serializable加了表级锁,第二个事务的操作会阻塞,等待第一个事务操作完
  • Serializable 性能最低;Read uncommitted 性能最高,数据安全性最差
# 查看事务隔离级别:
select @@transaction_isolation;
# 设置事务隔离级别:
set [session | global] transaction isolation level {read uncommitted | read committed | repeatable read | serializable };
# SESSION 是会话级别,表示只针对当前会话有效,GLOBAL 表示对所有会话有效

存储引擎

MySQL体系结构_20220315034329549927.png

MySQL体系结构层级含义_20220315034359342837.png

存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式。存储引擎是基于表而不是基于库的,所以存储引擎也可以被称为表引擎。默认存储引擎是InnoDB

show create table employee; -- 查询建表语句,默认存储引擎为InnoDB

show engines; -- 查询支持的存储引擎

-- 创建表并指定存储引擎
create table my_memory(
    name varchar(20),
    age tinyint unsigned
) engine = MyISAM;

InnoDB

InnoDB 是一种兼顾高可靠性和高性能的通用存储引擎,在 MySQL 5.5 之后,InnoDB 是默认的 MySQL 引擎

特点:

  • DML 操作遵循 ACID 模型,支持事务
  • 行级锁,提高并发访问性能
  • 支持外键约束,保证数据的完整性和正确性

文件:

  • xxx.ibd: xxx代表表名,InnoDB 引擎的每张表都会对应这样一个表空间文件,存储该表的表结构(frm、sdi)、数据和索引。 参数:innodb_file_per_table(默认打开),决定多张表共享一个表空间还是每张表对应一个表空间(默认后者)
# 查看MySql中的变量
show variables like 'innodb_file_per_table';

逻辑存储结构_20220316030616590001.png

MyISAM

MyISAM 是 MySQL 早期的默认存储引擎 特点:

  • 不支持事务,不支持外键
  • 支持表锁,不支持行锁
  • 访问速度快

文件:

  • xxx.sdi: 存储表结构信息
  • xxx.MYD: 存储数据
  • xxx.MYI: 存储索引

Memory

Memory 引擎的表数据是存储在内存中的,受硬件问题、断电问题的影响,只能将这些表作为临时表或缓存使用。

特点:

  • 存放在内存中,速度快
  • hash索引(默认)

文件:

  • xxx.sdi: 存储表结构信息

存储引擎的特点

特点InnoDBMyISAMMemory
存储限制64TB
事务安全支持--
锁机制行锁表锁表锁
B+tree索引支持支持支持
Hash索引--支持
全文索引支持(5.6版本之后)支持-
空间使用N/A
内存使用中等
批量插入速度
支持外键支持--

存储引擎的选择

在选择存储引擎时,应该根据应用系统的特点选择合适的存储引擎。对于复杂的应用系统,还可以根据实际情况选择多种存储引擎进行组合。

  • InnoDB: 如果应用对事务的完整性有比较高的要求,在并发条件下要求数据的一致性,数据操作除了插入和查询之外,还包含很多的更新、删除操作,则 InnoDB 是比较合适的选择
  • MyISAM: 如果应用是以读操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完整性、并发性要求不高,那这个存储引擎是非常合适的。
  • Memory: 将所有数据保存在内存中,访问速度快,通常用于临时表及缓存。Memory 的缺陷是对表的大小有限制,太大的表无法缓存在内存中,而且无法保障数据的安全性

电商中的足迹和评论适合使用 MyISAM 引擎,缓存适合使用 Memory 引擎。

索引

索引是帮助 MySQL 高效获取数据数据结构(有序) 。在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查询算法,这种数据结构就是索引。

优点:

  • 提高数据检索效率,降低数据库的IO成本
  • 通过索引列对数据进行排序,降低数据排序的成本,降低CPU的消耗

缺点:

  • 索引列也是要占用空间的
  • 索引大大提高了查询效率,但降低了更新的速度,比如 INSERT、UPDATE、DELETE

索引结构

索引结构描述
B+Tree最常见的索引类型,大部分引擎都支持B+树索引
Hash底层数据结构是用哈希表实现,只有精确匹配索引列的查询才有效,不支持范围查询
R-Tree(空间索引)空间索引是 MyISAM 引擎的一个特殊索引类型,主要用于地理空间数据类型,通常使用较少
Full-Text(全文索引)是一种通过建立倒排索引,快速匹配文档的方式,类似于 Lucene, Solr, ES
索引InnoDBMyISAMMemory
B+Tree索引支持支持支持
Hash索引不支持不支持支持
R-Tree索引不支持支持不支持
Full-text5.6版本后支持支持不支持

BTree

二叉树_20220316153214227108.png

红黑树_20220316163142686602.png

红黑树也存在大数据量情况下,层级较深,检索速度慢的问题。

为了解决上述问题,可以使用 B-Tree 结构。
B-Tree (多路平衡查找树) 以一棵最大度数(max-degree,指一个节点的子节点个数)为5(5阶)的 b-tree 为例(每个节点最多存储4个key,5个指针)

B-Tree结构_20220316163813441163.png

B-Tree 的数据插入过程动画参照:www.bilibili.com/video/BV1Kr…
演示地址:www.cs.usfca.edu/~galles/vis…

B+Tree

B+Tree结构图_20220316170700591277.png

演示地址:www.cs.usfca.edu/~galles/vis…

与 B-Tree 的区别:

  • 所有的数据都会出现在叶子节点
  • 叶子节点形成一个单向链表

MySQL 索引数据结构对经典的 B+Tree 进行了优化。在原 B+Tree 的基础上,增加一个指向相邻叶子节点的链表指针,就形成了带有顺序指针的 B+Tree,提高区间访问的性能。(双向循环链表)

结构图_20220316171730865611.png

Hash

哈希索引就是采用一定的hash算法,将键值换算成新的hash值,映射到对应的槽位上,然后存储在hash表中。

如果两个(或多个)键值,映射到一个相同的槽位上,他们就产生了hash冲突(也称为hash碰撞),可以通过链表来解决。

Hash索引原理图_20220317143226150679.png

特点:

  • Hash索引只能用于对等比较(=、in),不支持范围查询(betwwn、>、<、…)
  • 无法利用索引完成排序操作
  • 查询效率高,通常只需要一次检索就可以了,效率通常要高于 B+Tree 索引

存储引擎支持:

  • Memory
  • InnoDB: 具有自适应hash功能,hash索引是存储引擎根据 B+Tree 索引在指定条件下自动构建的

面试题

  1. 为什么 InnoDB 存储引擎选择使用 B+Tree 索引结构?
  • 相对于二叉树,层级更少,搜索效率高
  • 对于 B-Tree,无论是叶子节点还是非叶子节点,都会保存数据,这样导致一页中存储的键值减少,指针也跟着减少,要同样保存大量数据,只能增加树的高度,导致性能降低
  • 相对于 Hash 索引,B+Tree 支持范围匹配及排序操作

索引分类

分类含义特点关键字
主键索引针对于表中主键创建的索引默认自动创建,只能有一个PRIMARY
唯一索引避免同一个表中某数据列中的值重复可以有多个UNIQUE
常规索引快速定位特定数据可以有多个
全文索引全文索引查找的是文本中的关键词,而不是比较索引中的值可以有多个FULLTEXT

在 InnoDB 存储引擎中,根据索引的存储形式,又可以分为以下两种:

分类含义特点
聚集索引(Clustered Index)将数据存储与索引放一块,索引结构的叶子节点保存了行数据必须有,而且只有一个
二级索引(Secondary Index)将数据与索引分开存储,索引结构的叶子节点关联的是对应的主键可以存在多个

原理图_20220318194454880073.png

演示图_20220319215403721066.png

聚集索引选取规则:

  • 如果存在主键,主键索引就是聚集索引
  • 如果不存在主键,将使用第一个唯一(UNIQUE)索引作为聚集索引
  • 如果表没有主键或没有合适的唯一索引,则 InnoDB 会自动生成一个 rowid 作为隐藏的聚集索引

思考题

  1. 以下 SQL 语句,哪个执行效率高?为什么?
select * from user where id = 10;
select * from user where name = 'Arm';
-- 备注:id为主键,name字段创建的有索引

答:第一条语句,因为第二条需要回表查询,相当于两个步骤。

语法

# 创建索引:
create [unique | fulltext] index 索引名 on 表名 (表中的列名, ...);
# 如果不加 CREATE 后面不加索引类型参数,则创建的是常规索引
# 可以一个索引对应多列,如果对应单列就叫单列索引

# 查看索引:
show index from 表名;

# 删除索引:
drop index 索引名 on 表名;
# 例子
-- name字段为姓名字段,该字段的值可能会重复,为该字段创建索引
create index idx_user_name on tb_user(name);
-- phone手机号字段的值非空,且唯一,为该字段创建唯一索引
create unique index idx_user_phone on tb_user (phone);
-- 为profession, age, status创建联合索引
create index idx_user_pro_age_stat on tb_user(profession, age, status);
-- 为email建立合适的索引来提升查询效率
create index idx_user_email on tb_user(email);
-- 删除索引
drop index idx_user_email on tb_user;

使用规则

最左前缀法则

如果索引关联了多列(联合索引),要遵守最左前缀法则,最左前缀法则指的是查询从索引的最左列开始,并且不跳过索引中的列。

如果跳跃某一列,索引将部分失效(后面的字段索引失效)。

联合索引中,出现范围查询(<, >),范围查询右侧的列索引失效。可以用>=或者<=来规避索引失效问题。

# 例子
给 name, age, idcard 三个建立联合索引(顺序)

# 通过索引查询,三个都用上
slect * from employee where name = 'hsy', age = 24, idcard = '3411...'; 

# 通过索引查询,只用上name
select * from employee where name = 'hsy';

# 通过索引查询,用上name和age
select * from employee where name = 'hsy', age = 24;

# 通过索引查询,只用上name
select * from employee where name = 'hsy', idcard = '3411...';

# 通过索引查询,三个都用上
select * from employee where idcard = '3411...name = 'hsy', age = 24;

# 通过索引查询,只用上name和age
slect * from employee where name = 'hsy', age > 24, idcard = '3411...'; 

# 总结来说,不取决于写的顺序,而是取决于有没有出现

索引失效情况

  1. 在索引列上进行运算操作,索引将失效。如:explain select * from tb_user where substring(phone, 10, 2) = '15';
  2. 字符串类型字段使用时,不加引号,索引将失效。如:explain select * from tb_user where phone = 17799990015;,此处phone的值没有加引号
  3. 模糊查询中,如果仅仅是尾部模糊匹配,索引不会是失效;如果是头部模糊匹配,索引失效。如:explain select * from tb_user where profession like '%工程';,前后都有 % 也会失效。
  4. 用 or 分割开的条件,如果 or 其中一个条件的列没有索引,那么涉及的索引都不会被用到。
  5. 如果 MySQL 评估使用索引查询比全表查询更慢,则不使用索引。

SQL 提示

是优化数据库的一个重要手段,简单来说,就是在SQL语句中加入一些人为的提示来达到优化操作的目的。

# 例如,使用索引:
explain select * from tb_user use index(idx_user_pro) where profession="软件工程";
# 不使用哪个索引:
explain select * from tb_user ignore index(idx_user_pro) where profession="软件工程";
# 必须使用哪个索引:
explain select * from tb_user force index(idx_user_pro) where profession="软件工程";

use 是建议,实际使用哪个索引 MySQL 还会自己权衡运行速度去更改,force 就是无论如何都强制使用该索引

覆盖索引&回表查询

尽量使用覆盖索引(查询使用了索引,并且需要返回的列,在该索引中已经全部能找到),减少 select *

explain 中 extra 字段含义:
using index condition:查找使用了索引,但是需要回表查询数据
using where; using index;:查找使用了索引,但是需要的数据都在索引列中能找到,所以不需要回表查询

如果在聚集索引中直接能找到对应的行,则直接返回行数据,只需要一次查询,哪怕是select *;如果在辅助索引中找聚集索引,如select id, name from xxx where name='xxx';,也只需要通过辅助索引(name)查找到对应的id,返回name和name索引对应的id即可,只需要一次查询;如果是通过辅助索引查找其他字段,则需要回表查询,如select id, name, gender from xxx where name='xxx';

所以尽量不要用select *,容易出现回表查询,降低效率,除非有联合索引包含了所有字段

面试题:一张表,有四个字段(id, username, password, status),由于数据量大,需要对以下SQL语句进行优化,该如何进行才是最优方案:
select id, username, password from tb_user where username='itcast';

解:给username和password字段建立联合索引,则不需要回表查询,直接覆盖索引

前缀索引

当字段类型为字符串(varchar, text等)时,有时候需要索引很长的字符串,这会让索引变得很大,查询时,浪费大量的磁盘IO,影响查询效率,此时可以只将字符串的一部分前缀,建立索引,这样可以大大节约索引空间,从而提高索引效率。

# 语法:
create index idx_xxxx on 表名(列名(n));

前缀长度:可以根据索引的选择性来决定,而选择性是指不重复的索引值(基数)和数据表的记录总数的比值,索引选择性越高则查询效率越高,唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。

# 求选择性公式:
select count(distinct email) / count(*) from tb_user;
select count(distinct substring(email, 1, 5)) / count(*) from tb_user;

show index 里面的sub_part可以看到接取的长度

前缀索引搜索到数据以后,还会比对整个数据是否相同(之前只比对了前缀),然后再从二级索引里面向下查找看看是否还有满足条件的

单列索引&联合索引

单列索引:即一个索引只包含单个列
联合索引:即一个索引包含了多个列
在业务场景中,如果存在多个查询条件,考虑针对于查询字段建立索引时,建议建立联合索引,而非单列索引。

# 单列索引情况:
explain select id, phone, name from tb_user where phone = '17799990010' and name = '韩信';
# 这句只会用到phone索引字段

多条件联合查询时,MySQL优化器会评估哪个字段的索引效率更高,会选择该索引完成本次查询

设计原则

  1. 针对于数据量较大,且查询比较频繁的表建立索引
  2. 针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引
  3. 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高
  4. 如果是字符串类型的字段,字段长度较长,可以针对于字段的特点,建立前缀索引
  5. 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率
  6. 要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价就越大,会影响增删改的效率
  7. 如果索引列不能存储NULL值,请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含NULL值时,它可以更好地确定哪个索引最有效地用于查询

SQL优化

插入数据

普通插入:

  • 采用批量插入(一次插入的数据不建议超过1000条)
  • 手动提交事务
  • 主键顺序插入

大批量插入:
如果一次性需要插入大批量数据,使用insert语句插入性能较低,此时可以使用MySQL数据库提供的load指令插入。

# 客户端连接服务端时,加上参数 --local-infile(这一行在bash/cmd界面输入)
mysql --local-infile -u root -p
# 设置全局参数local_infile为1,开启从本地加载文件导入数据的开关
set global local_infile = 1;
select @@local_infile;
# 执行load指令将准备好的数据,加载到表结构中
load data local infile '/root/sql1.log' into table 'tb_user' fields terminated by ',' lines terminated by '\n';

主键优化

数据组织方式:在InnoDB存储引擎中,表数据都是根据主键顺序组织存放的,这种存储方式的表称为索引组织表(Index organized table, IOT)

页分裂:页可以为空,也可以填充一半,也可以填充100%,每个页包含了2-N行数据(如果一行数据过大,会行溢出),根据主键排列。
页合并:当删除一行记录时,实际上记录并没有被物理删除,只是记录被标记(flaged)为删除并且它的空间变得允许被其他记录声明使用。当页中删除的记录到达 MERGE_THRESHOLD(默认为页的50%),InnoDB会开始寻找最靠近的页(前后)看看是否可以将这两个页合并以优化空间使用。

MERGE_THRESHOLD:合并页的阈值,可以自己设置,在创建表或创建索引时指定

文字说明不够清晰明了,具体可以看视频里的PPT演示过程:www.bilibili.com/video/BV1Kr…

主键设计原则:

  • 满足业务需求的情况下,尽量降低主键的长度
  • 插入数据时,尽量选择顺序插入(否则容易页分裂),选择使用 AUTO_INCREMENT 自增主键
  • 尽量不要使用 UUID 做主键或者是其他的自然主键,如身份证号(长且无序)
  • 业务操作时,避免对主键的修改

order by优化

  1. Using filesort:通过表的索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区 sort buffer 中完成排序操作,所有不是通过索引直接返回排序结果的排序都叫 FileSort 排序
  2. Using index:通过有序索引顺序扫描直接返回有序数据,这种情况即为 using index,不需要额外排序,操作效率高(需要建立索引)

建立索引的顺序和排序顺序要一致(eg create index ... (age, name)),则查询时也要先按年龄再按名字排序

如果order by字段全部使用升序排序或者降序排序,则都会走索引,但是如果一个字段升序排序,另一个字段降序排序,则不会走索引,explain的extra信息显示的是Using index, Using filesort,如果要优化掉Using filesort,则需要另外再创建一个索引,如:create index idx_user_age_phone_ad on tb_user(age asc, phone desc);,此时使用select id, age, phone from tb_user order by age asc, phone desc;会全部走索引

总结:

  • 根据排序字段建立合适的索引,多字段排序时,也遵循最左前缀法则
  • 尽量使用覆盖索引
  • 多字段排序,一个升序一个降序,此时需要注意联合索引在创建时的规则(ASC/DESC)
  • 如果不可避免出现filesort,大数据量排序时,可以适当增大排序缓冲区大小 sort_buffer_size(默认256k)

group by优化

  • 在分组操作时,可以通过索引来提高效率
  • 分组操作时,索引的使用也是满足最左前缀法则的

如索引为idx_user_pro_age_stat,则句式可以是select ... where profession order by age,这样也符合最左前缀法则

limit优化

常见的问题如limit 2000000, 10,此时需要 MySQL 排序前2000000条记录,但仅仅返回2000000 - 2000010的记录,其他记录丢弃,查询排序的代价非常大。
优化方案:一般分页查询时,通过创建覆盖索引能够比较好地提高性能,可以通过覆盖索引加子查询形式进行优化

# 例子
# 此语句耗时很长
select * from tb_sku limit 9000000, 10;
# 通过覆盖索引加快速度,直接通过主键索引进行排序及查询
select id from tb_sku order by id limit 9000000, 10;
# 下面的语句是错误的,因为 MySQL 不支持 in 里面使用 limit 
select * from tb_sku where id in (select id from tb_sku order by id limit 9000000, 10);
# 通过内连接多表查询即可实现第一句的效果,并且能达到第二句的速度
select s.* from tb_sku as s, (select id from tb_sku order by id limit 9000000, 10) as a where s.id = a.id;

count优化

MyISAM 引擎把一个表的总行数存在了磁盘上,因此执行 count(*) 的时候会直接返回这个数,效率很高(前提是不使用where)
InnoDB 在执行 count(*) 时,需要把数据一行一行地从引擎里面读出来,然后累计计数。
优化方案:自己计数,如创建key-value表存储在内存或硬盘,或者是用redis

count的几种用法:

  • 如果count函数的参数(count里面写的那个字段)不是NULL(字段值不为NULL),累计值就加一,最后返回累计值
  • 用法:count(*)、count(主键)、count(字段)、count(1)
  • count(主键)跟count(*)一样,因为主键不能为空;count(字段)只计算字段值不为NULL的行;count(1)引擎会为每行添加一个1,然后就count这个1,返回结果也跟count(*)一样;count(null)返回0

各种用法的性能:

  • count(主键):InnoDB引擎会遍历整张表,把每行的主键id值都取出来,返回给服务层,服务层拿到主键后,直接按行进行累加(主键不可能为空)
  • count(字段):没有not null约束的话,InnoDB引擎会遍历整张表把每一行的字段值都取出来,返回给服务层,服务层判断是否为null,不为null,计数累加;有not null约束的话,InnoDB引擎会遍历整张表把每一行的字段值都取出来,返回给服务层,直接按行进行累加
  • count(1):InnoDB 引擎遍历整张表,但不取值。服务层对于返回的每一层,放一个数字 1 进去,直接按行进行累加
  • count(*):InnoDB 引擎并不会把全部字段取出来,而是专门做了优化,不取值,服务层直接按行进行累加

按效率排序:count(字段) < count(主键) < count(1) < count(*),所以尽量使用 count(\*)

update优化(避免行锁升级为表锁)

InnoDB 的行锁是针对索引加的锁,不是针对记录加的锁,并且该索引不能失效,否则会从行锁升级为表锁。

如以下两条语句:
update student set no = '123' where id = 1; 由于id有主键索引,所以只会锁这一行;
update student set no = '123' where name = 'test';,由于name没有索引,所以会把整张表都锁住进行数据更新,解决方法是给name字段添加索引

视图

视图(view)是一种虚拟存在的表,视图中的数据并不在数据库中实际存在,行和列数据来自定义视图的查询中使用的表,并且在使用视图时动态生成的

通俗地讲,视图只保存了查询的SQL逻辑,不保存查询结果,所以我们在创建视图的时候,主要的工作就落在创建这条SQL查询语句上

-- 创建
create [or replace] view 视图名称[(列名列表)] as select语句 [with[cascaded | local] check option];
-- 例子
create or replace view stu_v_1 as select id name from student where id <= 10;

-- 查询
-- 查看创建视图语句
show create view 视图名称;
-- 查看视图数据
select * from 视图名称;

-- 修改
-- 方式1:
create [or replace] view 视图名称[(列名列表)] as select语句 [with[cascaded | local] check option];
-- 方式2:
alter view 视图名称[(列名列表) as select语句 [with[cascaded | local] check option]

--删除
drop view [if exists] 视图名称;

视图也支持数据插入操作,但是视图本身是不存储数据的,所以其实还是会插入到视图的基表当中

检查选项

当使用 with check option 子句创建视图时,MySql会通过视图检查正在更改的每个行,例如插入,更新,删除,以使其符合视图的规定。MySql允许基于另一个视图创建视图,他还会检查依赖视图中的规则以保持一致性。为了确定检查的范围,MySql提供了两个选项:cascaded 和 local,默认值为cascade。例如上面要求id小于10,那么就不可以通过视图插入id大于10的数据

-- cascaded(级联) 和 local区别
-- cascaded
create view v1 as select id name from student where id <= 20;
create view v2 as select id name from v1 where id >= 10 with check cascaded option;
那么在对v2做插入操作时,也会检查v1的条件
insert into v2 (25, 'hsy'); -- 会报错,不满足v1要求
create view v3 as select id name from v2 where id <= 15;
insert into v3 (28, 'yt'); 
-- 会报错,不满足v1要求,检查选项存在继承,即使不检查自己的,如果父视图有检查,也要去检查

-- local
create view v1 as select id name from student where id <= 15;
create view v2 as select id name from v1 where id >= 10 with local check cascaded option;
create view v3 as select id name from v2 where id < 20;
insert into v2 (25, 'hsy'); -- 不会报错
insert into v3 (25, 'yt');  -- 不会报错

可以理解为 cascaded 会为祖先视图添加检查选项,而locla不会,local也会检查祖先视图是否有检查选项,有就检查,没有就不检查

视图的更新

要使视图可更新,视图中的行与基础表中的行必须存在一对一的关系。如果视图包含以下任何一项,则该视图不可更新:

  1. 聚合函数 sum(), min(), max, count()等
  2. distinct
  3. group by
  4. having
  5. union 或 union all

视图的作用

  • 简单:视图不仅可以简化用户对数据的理解,也可以简化他们的操作。那些被经常使用的查询可以被定义为视图,从而使得用户不必为以后的操作每次指定全部的条件。(封装?)
  • 安全:数据库可以授权,但不能授权到数据库特定的行和特定的列上。通过视图用户只能查询和修改他们所能见到的数据。
  • 数据独立:视图可帮助用户屏蔽真实表结构变化带来的影响。(? 没讲明白)