INnoDB 三特性 事务 外键 行级锁(开启事务时,查询后加FOR UPDATE)
MySQL 使用 InnoDB,在 默认隔离级别 ------ REPEATABLE READ(可重复读) 下 开启事务,执行 UPDATE
时默认会加行锁 只要事务没有提交 这条数据会锁住
Mysql概述
数据库(DataBase),简称:DB.存储数据的仓库,数据是有组织的进行存储
数据库管理系统(DataBase Management System),简称:DBMS.操作和管理数据库的大型软件
SQL(Structured Query Language):操作关系型数据库的编程语言,定义了一套操作关系型数据库统一标准
Windows中Mysql的启动命令
net start mysql80
net stop mysql80
Mysql客户端连接
mysql [-h 127.0.0.1] [-P 3306] -u root -p
系统管理类的 SQL 命令 元数据查询
查询所有数据库:SHOW DATABASES;
查询当前使用的数据库: SELECT DATABSES();
-
SHOW DATABASES;
:查看所有数据库 -
SHOW TABLES;
:查看当前数据库中的所有表 -
SHOW COLUMNS FROM 表名;
:查看表结构 -
SHOW INDEX FROM 表名;
:查看索引 -
SHOW GRANTS FOR 用户;
:查看权限
查询当前数据库所有表
SHOW TABLES;
查询表结构
DESC 表名;
查询指定表的建表语句
SHOW CREATE TABLE 表名
SQL分类
DDL(Data Definition Language):数据定义语言,用来定义数据库对象(数据库,表,字段)
常用关键字:
-
CREATE
:创建数据库、表、视图等 -
DROP
:删除数据库、表、视图等 -
ALTER
:修改表结构(增加/删除列、修改列类型等) -
TRUNCATE
:清空表数据(比 DELETE 快,不可回滚) -
RENAME
:重命名数据库对象
创建数据库:
CREATE DATABASES [ IF NOT EXISTS] 数据库名 [DEFAULT CHARSET 字符集] [ COLLATE 排序规则];
删除数据库
DORP DATABASE [IF EXISTS] 数据库名;
使用数据库
USE 数据库名
例如
create database itcast;
创建表
CREATE TABLE [IF NOT EXISTS] 表名(
字段1 字段1类型 [COMMENT 字段1注释],
字段2 字段2类型 [COMMENT 字段2注释]
)[COMMENT 表注释]
例如:
CREATE TABLE tb_user(id int comment '编号',name varchar(50) comment '姓名',age int comment '年龄',gender varchar(1) comment '性别') comment '用户表';
查看表结构:DESC tb_user;
查看指定表的建表语句:SHOW CREATE TABLE tb_user;
Mysql的数据类型
1.数值类型
有符号:指出现负数的范围 无符号:指没有负数的范围

DECIMAL(10, 2) 第一个参数10代表精度 第二个参数代表标度
精度表示最多可存储 10 位数字
标度表示 其中 2 位是小数位
例如:12345678.11
默认情况下在Navicat中即使不显示指定精度和标度
针对于Decimal类型需要显示指定精度和标度 不然无法存小数
Doubel即使0,0也可以存小数 应该是Navicat的显示误区
例如你希望用double表示分数 可以用double(4,1) 最多4位 小数点后保留1位

默认都是有符号的
指定无符号示例
CREATE TABLE users (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
age TINYINT UNSIGNED,
score INT,
balance DECIMAL(10,2) UNSIGNED
);
2.字符串类型

3.日期时间类型

练习

CREATE TABLE emp (
id INT COMMENT '编号',
workno VARCHAR(10) COMMENT '工号',
name VARCHAR(10) COMMENT '姓名',
gender CHAR(1) COMMENT '性别',
age TINYINT UNSIGNED COMMENT '年龄',
idcard CHAR(18) COMMENT '身份证号',
entrydate DATE COMMENT '入职时间'
) COMMENT='员工表';
DDL-表操作-修改和删除
添加字段
ALTER TABLE 表名 ADD 字段名 类型 [UNSIGNED] [NOT NULL] [DEFAULT 值] [COMMENT '说明'];
修改数据类型
ALTER TABLE 表名 MODIFY 字段名 新数据类型(长度) [UNSIGNED] [NOT NULL | NULL] [DEFAULT 值] [COMMENT '注释'];
修改字段名和字段类型 注意用CHANGE的话必须写"旧字段名"和"新字段名",哪怕名字不变!
ALTER TABLE 表名
CHANGE 旧字段名 新字段名 新数据类型(长度) [UNSIGNED] [NOT NULL | NULL] [DEFAULT 值] [COMMENT '注释'];
删除字段
ALTER TBALE 表名 DROP 字段名;
修改表名
ALTER TABLE 表名 RENAME 新表名;
删除表
删除整张表(连结构都没了)
DROP TABLE [IF EXISTS] 表名;
清空表数据,但保留表结构 重置表的自增计数器(如果有)不是逐行删除(像 DELETE
那样)
对于大表,TRUNCATE
比 DELETE FROM 表名
快得多 DELETE是DML操作
TRUNCATE TABLE 表名;
DDL-数据库操作总结
SHOW DATABASES; -- 查看所有数据库
CREATE DATABASE 数据库名; -- 创建数据库
USE 数据库名; -- 切换当前数据库
SELECT DATABASE(); -- 查看当前所用数据库
DROP DATABASE [IF EXISTS] 数据库名; -- 删除数据库(推荐加 IF EXISTS)
DDL-表操作总结
SHOW TABLES; -- 查看当前数据库中所有表
CREATE TABLE 表名 (
字段名 数据类型 [约束] [COMMENT '注释'],
...
); -- 创建表
DESC 表名; -- 查看表结构(字段+类型+约束)
SHOW CREATE TABLE 表名; -- 查看完整建表语句
-- 表结构变更(操作不能混用在一条 ALTER 中):
ALTER TABLE 表名 ADD 列名 类型(长度) ... ; -- 添加字段
ALTER TABLE 表名 MODIFY 列名 新类型(长度) ... ; -- 修改字段类型
ALTER TABLE 表名 CHANGE 旧名 新名 类型(长度) ... ; -- 改字段名+类型
ALTER TABLE 表名 DROP COLUMN 列名; -- 删除字段
ALTER TABLE 表名 RENAME TO 新表名; -- 修改表名
DROP TABLE [IF EXISTS] 表名; -- 删除表
DML(Data Manipulation Language):数据操作语言,用来对数据库表中的数据进行增删改
常用关键字:
-
INSERT
:插入数据 -
UPDATE
:更新数据 -
DELETE
:删除数据
添加数据INSERT
全量字段插入
INSERT INTO T_USER VALUES(1,"张三",18);
批量插入
INSERT INTO t_user VALUES(1,"张三",18),(2,"李四",19);
指定字段插入
INSERT INTO t_user(name,age)VALUES("王五",17),("赵六",19);
一些特殊用法
整张表数据复制
INSERT INTO 表B
SELECT * FROM 表A;
指定字段复制
INSERT INTO 表B(字段1, 字段2, ...)
SELECT 字段A1, 字段A2, ...
FROM 表A;
复制时加条件
INSERT INTO 表B
SELECT * FROM 表A
WHERE 条件;
跨数据库复制
INSERT INTO 新库.表B
SELECT * FROM 旧库.表A;
备份原表数据
CREATE TABLE emp_bak LIKE emp;
INSERT INTO emp_bak SELECT * FROM emp;
注意:插入数据时,指定字段顺序需要与值得顺序对应
字符串和日期型数据应该包含在引号中
插入的数据大小应该在字段的规定范围内
修改数据UPDATE
UPDATE 表名 SET 字段名1=值,字段名2=值2,...[WHERE 条件];
如果没有条件则修改整张表数据
删除数据DELETE
DELETE FROM 表名 [WHERE 条件];
DQL(Data Query Language):数据查询语言,用来查询数据库中表的记录
常用关键字:
-
SELECT
:查询数据 -
FROM
:指定数据来源的表 -
WHERE
:设置查询条件 -
GROUP BY
:分组 -
HAVING
:过滤分组后的结果 -
ORDER BY
:排序 -
JOIN
:多表连接查询
SELECT
字段列表
FROM
表名列表
WHERE
条件列表
GROUP BY
分组字段列表
HAVING
分组后条件列表
ORDER BY
排序字段列表
LIMIT
分页参数
执行顺序
from --- join --- on --- where --- group by --- having --- select --- distinct --- order by ---- limit
CREATE TABLE emp (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '编号',
workno VARCHAR(10) COMMENT '工号',
name VARCHAR(10) COMMENT '姓名',
gender CHAR(1) COMMENT '性别',
age TINYINT UNSIGNED COMMENT '年龄',
idcard CHAR(18) COMMENT '身份证号',
workaddress VARCHAR(50) COMMENT '工作地址',
entrydate DATE COMMENT '入职时间'
) COMMENT='员工表';
INSERT INTO emp (id, workno, name, gender, age, idcard, workaddress, entrydate)
VALUES
(1, '1', '柳岩', '女', 20, '123456789012345678', '北京', '2000-01-01'),
(2, '2', '张无忌', '男', 18, '123456789012345670', '北京', '2005-09-01'),
(3, '3', '韦一笑', '男', 38, '123456789712345670', '上海', '2005-08-01'),
(4, '4', '赵敏', '女', 18, '123456757123845670', '北京', '2009-12-01'),
(5, '5', '小昭', '女', 16, '123456789012345678', '上海', '2007-07-01'),
(6, '6', '杨逍', '男', 28, '12345678931234567X', '北京', '2006-01-01'),
(7, '7', '范遥', '男', 40, '123456789212345670', '北京', '2005-05-01'),
(8, '8', '黛绮丝', '女', 38, '123456157123465670', '天津', '2015-05-01'),
(9, '9', '范凉凉', '女', 45, '123156789012345678', '北京', '2010-04-01'),
(10, '10', '陈友谅', '男', 53, '123456789012345670', '上海', '2011-01-01'),
(11, '11', '张士诚', '男', 55, '12356789712345670', '江苏', '2015-05-01'),
(12, '12', '常遇春', '男', 32, '12346475712345670', '北京', '2004-02-01'),
(13, '13', '张三丰', '男', 88, '12365678712345678', '江苏', '2020-11-01'),
(14, '14', '灭绝', '女', 65, '123456791297123456', '西安', '2019-05-01'),
(15, '15', '胡青牛', '男', 70, '12345674971234567X', '西安', '2018-04-01'),
(16, '16', '周芷若', '女', NULL, '123456789012345670', '北京', '2012-06-01');
基本查询
SELECT 字段1,字段2,字段3,...FROM 表名;
SELECT * FROM 表名;
设置别名
SELECT 字段1 [AS 别名1],字段2 [AS 别名2],.... FROM 表名;
去除重复记录
SELECT DISTINCT 字段列表 FROM 表名;
基本查询练习
1.查询指定字段 name,workno,age
SELECT name,workno,age FROM emp;
2.查询所有字段
SELECT * FROM emp; #最好列出所有字段 不要用*
3.查询所有员工的工作地址,起别名
SELECT workaddress AS address FROM emp;
4.查询公司员工的上班地址(不要重复)
SELECT DISTINCT workaddress AS address FROM emp;
条件查询(WHERE)
SELECT 字段列表 FROM 表名 WHERE 条件列表;
条件:

条件查询练习
查询年龄等于88的员工
SELECT * FROM emp where age=88;
查询年龄小于20的员工信息
SELECT * FROM emp WHERE age<20;
查询年龄小于等于20的员工
SELECT * FROM emp WHERE age<=20;
查询没有身份证号的员工信息
SELECT * FROM emp WHERE idcard IS NULL;
查询有身份证号的员工信息
SELECT * FROM emp WHERE idcard IS NOT NULL;
查询年龄不等于88的员工信息
SELECT * FROM emp WHERE age!=88;
SELECT * FROM emp WHERE age<>88;
查询年龄在15岁(包含)到20岁(包含的)员工信息;
SELECT * FROM emp
WHERE age >= 15 AND age <= 20;
SELECT * FROM emp
WHERE age >= 15 &&age <= 20;
SELECT * FROM emp
WHERE age BETWEEN 15 AND 20;等价于age >= 15 AND age <= 20
注意:使用BETWEEN 参数1 AND 参数2 时候 参数1<=参数2
查询性别为女且年龄小于25岁员工信息
SELECT * FROM emp WHERE gender='女' AND age<25;
SELECT * FROM emp WHERE gender='女' && age<25;
查询年龄等于18或20或40的员工信息
SELECT * FROM emp WHERE age = 18 OR age=20 OR age=40;
SELECT * FROM emp WHERE age IN(18,20,40);
查询姓名为2个字的员工信息
SELECT * FROM emp WHERE name like '__'; #两个下划线 一个_代表一个字符
SELECT * FROM emp
WHERE CHAR_LENGTH(name) = 2;
查询身份证最后一位是X的员工信息
SELECT * FROM emp
WHERE idcard LIKE '%X';
SELECT * FROM emp
WHERE idcard LIKE '_________________X'; #前面写17个下划线也行
聚合函数(COUNT MAX MIN AVG SUM)
将一列数据做为一个整体,进行纵向计算
常见聚合函数
COUNT 统计数量
MAX 最大值
MIN 最小值
AVG 平均值
SUM 求和
语法:
SELECT 聚合函数(字段列表) FROM 表名;
SELECT COUNT(*) FROM emp;
SELECT COUNT(1) FROM emp; #效率上和COUNT(*)几乎等价
SELECT COUNT(age) FROM emp; #会自动排除age为null的行
注意:如果指定列名,为null的列不参与统计,会自动排除NULL的列
聚合函数练习
统计该企业员工数量
SELECT COUNT(*) FROM emp;
统计该企业员工平均年龄
SELECT AVG(age) FROM emp;
统计该企业员工最大年龄
SELECT MAX(age) FROM emp;
统计该企业员工的最小年龄
SELECT MIN(age) FROM emp;
统计西安地区员工年龄之和
SELECT * FROM emp WHERE workaddress='西安';
分组查询(GROUP BY)
语法:
SELECT 字段列表 FROM 表名 [WHERE 条件] GROUP BY 分组字段名 [HAVING 分组后过滤条件];
WHERE与HAVING的区别
执行时机不同:WHERE执行在GROUP BY之前,不满足WHERE条件的不参与GROUP BY,HAVING是GROUP BY之后对结果进行过滤
判断条件不同:WHERE不能对聚合函数进行判断,而HAVING可以
分组查询练习
根据性别分组,统计男性员工和女性员工数量
SELECT gender, COUNT(*) AS total
FROM emp
GROUP BY gender; #分组字段不写入查询 查询变得没有意义
根据性别分组,统计男性员工和女性员工的平均年龄
SELECT gender, AVG(age) AS total
FROM emp
GROUP BY gender;
查询年龄小于45的员工,并根据工作地址分组,获取员工数量大于等于3的工作地址
SELECT workaddress,COUNT(*) total FROM emp where age<45 GROUP BY workaddress HAVING total>3;
SELECT workaddress,COUNT(*) total FROM emp where age<45 GROUP BY workaddress HAVING COUNT(*)>3; #select 之后起了别名 除了ORDER BY之后 HAVING之后也是可以的
注意:
执行顺序 WHERE --- GROUP BY --- 聚合函数 ---HAVING
分组之后,查询的一般为聚合函数和分组字段,查询其他字段无任何意义,一般来说如果用了分组查询,分组的字段不写入查询也会变得无意义(因为不知道这是个什么) 除非就是写死,只有自己知道这是个啥
1. FROM
2. JOIN
3. ON
4. WHERE
5. **GROUP BY** ← 把数据分组
6. **聚合函数计算**(如 COUNT、SUM)
7. HAVING
8. SELECT
9. ORDER BY
10. LIMIT
排序查询(ORDER BY)
语法:
SELECT 字段列表 FROM 表名 ORDER BY 字段1 排序方式1,字段2 排序方式2;
ASC 代表升序 默认值
DESC 降序
如果多字段排序,当第一个字段值相同时,才会根据第二个字段进行排序
排序查询练习
根据年龄对公司的员工进行升序排序
SELECT * FROM emp ORDER BY age; #默认ASC 可以不写
根据入职时间,对员工进行降序排序
SELECT * FROM emp ORDER BY entrydate DESC;
根据年龄对公司员工进行升序排序,年龄相同按入职时间进行降序排序
SELECT * FROM emp ORDER BY age,entrydate DESC;
分页查询(LIMIT)
语法:
SELECT 字段列表 FROM emp LIMIT 起始索引,查询记录数;
如果将pageNum当页数 pageSize当每页显示条数
注意:该公式不能直接当SQL写入 需要计算好(pageNum-1)*pageSize,pageSize
SELECT 字段列表 FROM emp LIMIT (pageNum-1)*pageSize,pageSize;
注意:分页查询,不同数据库有不同实现,Mysql中是LIMIT
DQL练习
查询年龄为20,21,22,23岁的员工信息
SELECT * FROM emp WHERE age=20 OR age=21 OR age=22 OR age=23;
SELECT * FROM emp WHERE age BETWEEN 20 AND 23;
SELECT * FROM emp WHERE age IN(20,21,22,23);
查询性别为男,并且年龄在20-40岁(都包含)以内的姓名为三个字的员工信息
SELECT * FROM emp WHERE gender='男' AND age BETWEEN 20 AND 40 AND name LIKE '___';
统计员工表中,年龄小于60岁的,男性员工共和女性员工的人数
SELECT gender,COUNT(*) FROM emp WHERE age<60 GROUP BY gender;
查询所有年龄小于35岁员工的姓名,年龄,并对查询结果按年龄升序排序,如果年龄相同按入职时间降序排序
SELECT name, age
FROM emp
WHERE age <= 35
ORDER BY age, entrydate DESC;
查询性别为男,且年龄在20-40岁(包含)以内的前5个员工信息,对查询的结果按年龄升序排序,年龄相同按入职时间升序排序
SELECT * FROM emp WHERE gender='男' AND age BETWEEN 20 AND 40 ORDER BY age,entrydate LIMIT 5;
DCL(Data Control Language):数据控制语言,用来创建数据库用户,控制数据库的访问权限
常用关键字:
-
GRANT
:授予权限 -
REVOKE
:撤销权限 -
DENY
:拒绝权限(部分数据库支持,如 SQL Server)
DCL管理用户
1.查询用户
USE mysql;
SELECT * FROM user; #这个user是表名
2.创建用户
CREATE USER '用户名'@'主机名' IDENTIFIED BY '密码'; #这里的USER是关键字
3修改用户密码
ALTER USER '用户名'@'主机名' IDENTIFIED WITH mysql_native_password BY '新密码';
4.删除用户
DROP USER '用户名'@'主机名';
创建用户root1 可以从任何地址来连接数据库 密码是abc123456
CREATE USER 'root2'@'%' IDENTIFIED BY 'abc123456';
修改用户登录密码
ALTER USER 'root1'@'%' IDENTIFIED WITH mysql_native_password BY 'abc1234561';
删除用户root1
DROP USER 'root1'@'%';
TCL管理练习
创建用户'hrui',只能够在当前主机localhost访问,密码'123456'
#该用户创建之后没有权限 只能看到information_schema一张表
CREATE USER 'hrui'@'localhost' IDENTIFIED BY '123456';
修改用户登录密码
ALTER USER 'root1'@'%' IDENTIFIED WITH mysql_native_password BY 'abc1234561';
删除用户root1
DROP USER 'root1'@'%';
权限设置 默认创建用户后 该用户登录之后 没有权限 只能看到information_schema和performance_schema两张表

查询用户权限
SHOW GRANTS FOR '用户名'@'主机名';

新创建的用户只能看到

授予权限
GRANT 权限列表 ON 数据库名.表名 TO '用户名'@'主机名'; #如果 *.* 表示所有数据库,所有表

GRANT ALL ON *.* TO 'root1'@'%'; #授予root1所有权限 注意:RDS上不能这么做 不能给与非root用户所有的权限 可以设置具体的库和具体的表
GRANT ALL PRIVILEGES ON testdb.* TO 'root1'@'%';和GRANT ALL ON testdb.* TO 'root1'@'%';效果事一样的
撤销权限
REVOKE all on *.* FROM 'root1'@'%'; 撤销所有权限
注意:多个权限用逗号分隔,授权时数据库和表名可以使用 * 进行统配 *.* 就是所有库所有表
阿里云RDS不允许创建非root用户 的 *.* 全库权限
重点
1.用户管理
CREATE USER '用户名'@'主机名' IDENTIFIED BY '密码'; #可以在任何库执行 不需要指定库
ALTER USER '用户名'@'主机名' IDENTIFIED WITH mysql_native_password BY '新密码';
DROP USER '用户名'@'主机名';#可以在任何库执行 不需要指定库 就是不用use xxx
2.权限控制
GRANT 权限列表 ON 数据库名.表名 TO '用户名'@'主机名';
REVOKE 权限列表 ON 数据库名.表名 FROM '用户名'@'主机名';
select * from user; #需要指定数据库 use mysql;
TCL(Transaction Control Language):事务控制语句,用于控制事务
常用关键字:
-
BEGIN
/START TRANSACTION
:开始事务 -
COMMIT
:提交事务 -
ROLLBACK
:回滚事务 -
SAVEPOINT
:设置保存点 -
SET TRANSACTION
:设置事务属性
函数
指一段可以直接被另一段程序调用的程序或代码
字符串函数

CONCAT 参数可以是1个或者多个 依次拼接参数 模糊查询时候经常用到
注意SUBSTRING start是从1开始的 TRIM 无法去掉str中间的空格
数值函数
常用的数值函数

日期函数

流程函数

常用示例

END是为了结束语法 必写 然后取个别名 ELSE 可以不写 不写的话都没有匹配到就返回NULL

约束
约束:作用于表中字段上的规则,用于限制存储在表中的数据
目的:保证数据库中的数据的正确,有效性完整性

各种约束示例
DEFAULT NULL, DEFAULT 0, DEFAULT 'XXX'


CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID唯一标识',
name VARCHAR(10) NOT NULL UNIQUE COMMENT '姓名',
age INT CHECK (age > 0 AND age <= 120) COMMENT '年龄',
status CHAR(1) DEFAULT '1' COMMENT '状态',
gender CHAR(1) COMMENT '性别'
) COMMENT='用户表';

约束是作用于表中字段上的,可以在创建表/修改表的时候添加约束
外键约束
当一张表中的字段引用了另一张表的主键时,这个字段称为"外键" (foreign key),这个关系称为外键约束。
这样 如果要删除dept表中数据前,先要删除emp表中关联数据
示例
后面加ON
多表查询
关于sql92和sql99写法
sql92写法 两张表用","分割,关联关系放在where后面
FROM emp, dept
WHERE emp.dept_id = dept.id;
sql99语法
FROM emp
INNER JOIN dept ON emp.dept_id = dept.id;
多表关系
在关系型数据库中,常见的表与表之间关系有这 三种:一般现在都不建议外键
1.一对一
CREATE TABLE user (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50)
);
CREATE TABLE user_detail (
id INT PRIMARY KEY AUTO_INCREMENT, -- 自己的主键
user_id INT UNIQUE, -- 外键 + 唯一约束实现一对一
address VARCHAR(100),
phone VARCHAR(20),
FOREIGN KEY (user_id) REFERENCES user(id)
);
查询示例
SELECT u.username, d.phone
FROM user u
LEFT JOIN user_detail d ON u.id = d.user_id
WHERE u.id = 1;
A 表的一条记录只能对应 B 表的一条记录,反之亦然
2.一对多
A 表一条记录可以对应 B 表的多条记录
CREATE TABLE dept (
id INT PRIMARY KEY,
name VARCHAR(50)
);
CREATE TABLE emp (
id INT PRIMARY KEY,
name VARCHAR(50),
dept_id INT,
FOREIGN KEY (dept_id) REFERENCES dept(id)
);
查询示例,查某个部门下的员工
SELECT d.name AS 部门名, e.name AS 员工名
FROM dept d
LEFT JOIN emp e ON d.id = e.dept_id
WHERE d.id = 1;
查某个员工在哪个部门
SELECT e.name AS 员工名, d.name AS 部门名
FROM emp e
JOIN dept d ON e.dept_id = d.id
WHERE e.id = 1;
3.多对多
A 表中的记录可以对应 B 表的多条记录,B 表中的记录也能对应 A 表的多条记录 需要一张中间表保存A表ID和B表ID
CREATE TABLE student (
id INT PRIMARY KEY,
name VARCHAR(50)
);
CREATE TABLE course (
id INT PRIMARY KEY,
name VARCHAR(50)
);
CREATE TABLE student_course ( -- 中间表
student_id INT,
course_id INT,
PRIMARY KEY (student_id, course_id), #可以用这个做主键 因为student_id和course_id 一定唯一,也可以单独创建主键 自行维护 外键现在一般都不建议
FOREIGN KEY (student_id) REFERENCES student(id),
FOREIGN KEY (course_id) REFERENCES course(id)
);
查询示例:查某个员工选了哪些课程
SELECT s.name AS 学生, c.name AS 课程
FROM student s
JOIN student_course sc ON s.id = sc.student_id
JOIN course c ON sc.course_id = c.id
WHERE s.id = 1;
多表查询创建几张关联表
#创建部门表并插入数据
CREATE TABLE dept (
id INT AUTO_INCREMENT COMMENT 'ID' PRIMARY KEY,
name VARCHAR(50) NOT NULL COMMENT '部门名称'
) COMMENT '部门表';
INSERT INTO dept (id, name) VALUES
(1, '研发部'),
(2, '市场部'),
(3, '财务部'),
(4, '销售部'),
(5, '总经办'),
(6, '人事部');
#创建员工表并插入数据
CREATE TABLE emp (
id INT AUTO_INCREMENT COMMENT 'ID' PRIMARY KEY,
name VARCHAR(50) NOT NULL COMMENT '姓名',
age INT COMMENT '年龄',
job VARCHAR(20) COMMENT '职位',
salary INT COMMENT '薪资',
entrydate DATE COMMENT '入职时间',
managerid INT COMMENT '直属领导ID',
dept_id INT COMMENT '部门ID'
) COMMENT '员工表';
INSERT INTO emp (id, name, age, job, salary, entrydate, managerid, dept_id) VALUES
(1, '金庸', 66, '总裁', 20000, '2000-01-01', NULL, 5),
(2, '张无忌', 20, '项目经理', 12500, '2005-12-05', 1, 1),
(3, '杨逍', 33, '开发', 8400, '2000-11-03', 2, 1),
(4, '韦一笑', 48, '开发', 11000, '2002-02-05', 2, 1),
(5, '常遇春', 43, '开发', 10500, '2004-09-07', 3, 1),
(6, '小昭', 19, '程序员鼓励师', 6600, '2004-10-12', 2, 1),
(7, '灭绝', 60, '财务总监', 8500, '2002-09-12', 1, 3),
(8, '周芷若', 19, '会计', 48000, '2006-06-02', 7, 3),
(9, '丁敏君', 23, '出纳', 5250, '2009-05-13', 7, 3),
(10, '赵敏', 20, '市场部总监', 12500, '2004-10-12', 1, 2),
(11, '鹿杖客', 56, '职员', 3750, '2006-10-03', 10, 2),
(12, '鹤笔翁', 19, '职员', 3750, '2007-05-09', 10, 2),
(13, '方东白', 19, '职员', 5500, '2009-02-12', 10, 2),
(14, '张三丰', 88, '销售总监', 14000, '2004-10-12', 1, 4),
(15, '俞莲舟', 38, '销售', 4600, '2004-10-12', 14, 4),
(16, '宋远桥', 40, '销售', 4600, '2004-10-12', 14, 4),
(17, '陈友谅', 42, NULL, 2000, '2011-10-12', 1, NULL);
#在员工表中创建外键
ALTER TABLE emp
ADD CONSTRAINT fk_emp_dept_id
FOREIGN KEY (dept_id) REFERENCES dept(id);
笛卡尔积演示
SELECT * FROM emp,dept;
没有 WHERE
条件或 JOIN
连接条件,数据库就会把 emp
表中的每一行,与 dept
表中的每一行都组合一遍。

-
emp
有 17 条数据 -
dept
有 6 条数据 -
17 × 6 = 102 行
需要写连接条件
多表查询分类
多表查询分类:
1.连接查询
1.1)内连接:相当于查询A,B交集部门数据
1.2)外连接:
2.1)左外连接:查询左表所有数据,以及两张表交集部门数据
2.2)右外连接:查询右表所有数据,以及两张表交集部门数据
1.3)自连接:当前表与自身的连接查询,自连接必须使用表别名
2.子查询:把单表分为两张表,需要有连接条件
内连接
返回的是两张表交集部门的数据
sql92写法(隐式写法)
SELECT 字段列表 FROM 表1,表2 WHERE 表1.字段=表2.字段;
sql99写法(显式写法)
SELECT 字段列表
FROM 表1 [INNER] JOIN 表2 ON 表1.字段 = 表2.字段;
内连接练习
查询每一个员工的姓名,及关联的部门的名称(隐式内连接实现)
SELECT a.name,b.name deptName FROM emp a,dept b WHERE a.dept_id=b.id;
查询每一个员工的姓名,及关联的部门名称(显式内连接实现)
SELECT a.name,b.name deptName FROM emp a JOIN dept b ON a.dept_id=b.id;
外连接
左外连接
语法
SELECT 字段列表 FROM 表1 LEFT [OUTER] JOIN 表2 ON 连接条件;
以左表为主,查左表所有数据,及交集数据 交集没有的以null填充
右外连接
语法
SELECT 字段列表 FROM 表1 RIGHT [OUTER] JOIN 表2 ON 连接条件;
以右表为主,查右表所有数据,及交集数据 交集没有的以null填充
自连接
自连接查询可以是内连接查询也可以是外连接查询
SELECT 字段列表 FROM 表1 别名A JOIN 表2 别名B ON 连接条件;
连接条件
员工的manage_id=老板表.id
联合查询UNION,UNION ALL
UNION查询,就是把多次查询的结果合并起来,形成一个新的查询结果集
语法
SELECT 字段1, 字段2, ...
FROM 表1
WHERE 条件
UNION [ALL]
SELECT 字段1, 字段2, ...
FROM 表2
WHERE 条件;
注意:需要保证查询出来的字段名 或者别名是相同的并且列数是相同的

子查询
定义:SQL语句中嵌套SELECT语句,称为嵌套查询,又称为子查询
SELECT * FROM 表1 WHERE column1=(SELECT column1 FROM 表2)
子查询的写法很多
子查询外部的语句可以是INSERT/UPDATE/DELETE/SELECT的任何一个
① SELECT *
FROM A
WHERE EXISTS (
SELECT id
FROM B
WHERE B.aid = A.id
)
#为什么用DISTINCT 如果A和B是1对多情况 A表数据匹配B表多条会出现重复
② SELECT DISTINCT A.*
FROM A
JOIN B ON A.id = B.aid
③ SELECT *
FROM A
WHERE A.id IN (
SELECT B.aid
FROM B
)
三种写法结果是等价的
子查询根据结果不同,可以分为
1.标量子查询(子查询结果为单个值)
2.列子查询(子查询结果为一列)
3.行子查询(子查询结果为一行)
4.表子查询(子查询结果为多行多列)
根据子查询位置,分为:WHERE之后,FROM 之后,SELECT之后.
标量子查询
子查询结果为单个值
常用的操作符: = <> > >= < <=
查询销售部的所有的员工信息
SELECT * FROM emp WHERE dept_id=(SELECT id FROM dept WHERE name='销售部')
查询在方东白入职之后入职的员工信息
SELECT * FROM emp WHERE entrydate>(SELECT entrydate FROM emp WHERE name='方东白')
列子查询
子查询结果为一列
常用操作符:IN NOT IN ANY SOME ALL

查询销售部和市场部的所有员工信息
SELECT * FROM emp WHERE dept_id IN(SELECT id FROM dept WHERE name IN('销售部','市场部'))
查询比财务部所有员工工资都高的员工 (这里将财务部周芷若的工资改为8000)插入数据时候数据有问题
SELECT * FROM emp WHERE salary >(SELECT MAX(salary) FROM emp WHERE dept_id=(SELECT id FROM dept WHERE name='财务部'))
SELECT * FROM emp WHERE salary >all(SELECT salary FROM emp WHERE dept_id=(SELECT id FROM dept WHERE name='财务部'))
查询比研发部其中任意一人工资高的员工信息
SELECT * FROM emp WHERE salary>any(SELECT salary FROM emp WHERE dept_id=(SELECT id FROM dept WHERE name='研发部'))
行子查询
子查询结果为一行(可以是多列)
常用操作符: = <> IN NOT IN
查询与张无忌的薪资及直属领导相同的员工信息
薪资相同
SELECT salary FROM emp WHERE salary=(SELECT salary FROM emp WHERE name='张无忌')
直属领导相同
SELECT manageid FROM emp WHERE name='张无忌'
SELECT *
FROM emp
WHERE salary = (
SELECT salary FROM emp WHERE name = '张无忌'
)
AND managerid = (
SELECT managerid FROM emp WHERE name = '张无忌'
);
可以这么写
SELECT *
FROM emp
WHERE (salary, managerid) = (
SELECT salary, managerid
FROM emp
WHERE name = '张无忌'
);
表子查询
子查询结果为多行多列
常用操作符:IN
查询与鹿杖客 宋远桥的职位和薪资相同的员工信息
鹿杖客 宋远桥的职位和薪资
SELECT job,salary FROM emp WHERE name IN('鹿杖客','宋远桥')
SELECT * FROM emp WHERE (job,salary) IN(SELECT job,salary FROM emp WHERE name IN('鹿杖客','宋远桥'))
查询入职日期是2006-01-01之后的员工信息,及其部门信息
SELECT
A.*,
FROM emp A
LEFT JOIN dept B ON A.dept_id = B.id
WHERE A.entrydate > '2006-01-01';
也可以这样
SELECT e.*, d.*
FROM (
SELECT * FROM emp WHERE entrydate > '2006-01-01'
) e
LEFT JOIN dept d ON e.dept_id = d.id;
多表查询案例
新增下面这张表 工资等级表(妈了个巴子资本主义啊) 孙中山先生是伟大的'理想家'......
CREATE TABLE salgrade (
grade INT COMMENT '等级',
losal INT COMMENT '最低工资',
hisal INT COMMENT '最高工资'
) COMMENT='薪资等级表';
INSERT INTO salgrade VALUES
(1, 0, 3000),
(2, 3001, 5000),
(3, 5001, 8000),
(4, 8001, 10000),
(5,10001, 15000),
(6,15001, 20000),
(7,20001, 25000),
(8,25001, 30000);
查询员工的姓名,年龄,职位,部门信息
SELECT a.name,a.age,a.job,b.name deptName FROM emp a LEFT JOIN dept b ON a.dept_id=b.id
查询年龄小于30岁的员工姓名,年龄,职位,部门信息
SELECT a.name,a.age,a.job,b.name deptName FROM emp a LEFT JOIN dept b ON a.dept_id=b.id WHERE a.age<30
查询拥有员工的部门ID,部门名称 (查询交集部分去重)
SELECT id,name FROM dept WHERE id IN(SELECT dept_id FROM emp WHERE dept_id is not null)
SELECT DISTINCT d.id,d.name FROM emp e,dept d WHERE e.dept_id=d.id
查询所有年龄大于40岁的员工,及其归属的部门名称,如果员工没有分配部门,也需要展示出来
SELECT a.name,b.name deptName FROM emp a LEFT JOIN dept b ON a.dept_id=b.id WHERE a.age>40
查询所有员工的工资等级
SELECT
b.grade
FROM
emp a LEFT JOIN salgrade b
ON
a.salary BETWEEN b.losal AND b.hisal
或者
SELECT a.name, b.grade
FROM emp a
LEFT JOIN salgrade b
ON a.salary >= b.losal AND a.salary <= b.hisal;
查询研发部所有员工的信息及工资等级
SELECT
c.grade,
b.NAME deptName
FROM
emp a
JOIN dept b ON a.dept_id = b.id JOIN salgrade c ON a.salary BETWEEN c.losal
AND c.hisal
WHERE
b.NAME = '研发部'
查询研发部员工的平均工资
SELECT
a.name AS deptName,
AVG(b.salary) AS avgSalary
FROM dept a
LEFT JOIN emp b ON a.id = b.dept_id
WHERE a.name = '研发部'
GROUP BY a.name;
查询工资比灭绝高的员工信息
SELECT * FROM emp WHERE salary>(SELECT salary FROM emp WHERE name='灭绝')
查询比平均薪资高的员工信息
SELECT * FROM emp WHERE salary>(SELECT AVG(salary) FROM emp)
查询低于本部门平均工资的员工信息
SELECT a.*
FROM emp a
JOIN (
SELECT dept_id, AVG(salary) AS avg_salary
FROM emp
GROUP BY dept_id
) AS b ON a.dept_id = b.dept_id
WHERE a.salary < b.avg_salary;
查询所有的部门信息,并统计部门的员工人数
SELECT deptName, COUNT(*)
FROM (SELECT a.*, b.name deptName
FROM emp a
JOIN dept b ON a.dept_id = b.id) t
GROUP BY deptName
事务
事务是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,这些操作要么同时成功,要么同时失败
默认情况下,使用Mysql默认自动提交事务的
创建事务演示表
CREATE TABLE account (
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
name VARCHAR(10) COMMENT '姓名',
money INT COMMENT '余额'
) COMMENT='账户表';
INSERT INTO account (id, name, money)
VALUES
(NULL, '张三', 2000),
(NULL, '李四', 2000);
事务操作
以下两种都是临时会话级别设置事务手动提交
方式1:
开启事务: START TRANSACTION 或者 BEGIN
提交事务: COMMIT
回滚事务: ROLLBACK
方式2
可以通过上面方式 也可以设置
SELECT @@autocommit; #如果是1 自动提交 0手动提交
SET @@autocommit = 0;
方式2不需要START TRANSACTION 或者 BEGIN 可以直接用COMMIT ROLLBACK
一般只需要了解会话级别事务即可
方式1

方式2

事务四大特性
1. 原子性(Atomicity)
事务是不可分割的最小操作单元,要么全部成功,要么全部失败。
💡 比如转账:扣钱成功但收钱失败,就要全部回滚。
2. 一致性(Consistency)
事务执行后,必须使数据库从一个一致状态变为另一个一致状态。
💡 比如转账后,张三减少的钱和李四增加的钱总数不变,前后一致。
3. 隔离性(Isolation)
多个事务并发执行时,要互不干扰,互不影响,数据库提供隔离机制保障这一点。
💡 比如你在查库存时,别人的下单操作不会影响你看到的结果。
4. 持久性(Durability)
事务一旦提交,对数据库的修改就是永久性的,即使系统崩溃也不会丢失。
💡 比如提交转账后,即使断电重启,数据也已经改了。

并发事务问题
两个或多个事务在"同时"对同一份数据(同一条)进行操作或读取,从而引发数据不一致、错乱等问题。
例如张三的银行卡
在一个事务中
SELECT money FROM account WHERE name = '张三'; -- 得到 1000
UPDATE account SET money = 1000 - 100 WHERE name = '张三'; -- 更新为 900
在另外一个事务中SELECT money FROM account WHERE name = '张三'; -- 也看到 1000!
UPDATE account SET money = 1000 - 200 WHERE name = '张三'; -- 更新为 800
结果谁最后提交就覆盖了另一个,导致少减了一笔!
脏读
一个事务 读取了另一个事务尚未提交的数据
Read Uncommitted(读未提交)发生
举例
当A事务开启,执行 但是还没有提交
UPDATE account SET money = 0 WHERE name = '张三';
此时事务 B 执行
SELECT money FROM account WHERE name = '张三'; -- 查到 0(但其实没提交)
然后事务 A 回滚了,张三余额还是原来的,但事务 B 却读到了"脏数据"
不可重复读
同一个事务中,前后两次读取同一条数据,结果不一样(因为别的事务修改了数据并提交了)
Read Committed(读已提交)会发生
举例
当事务A开启
SELECT money FROM account WHERE name = '张三'; -- 第一次查到 1000
此时事务B对该数据进行了操作
UPDATE account SET money = 500 WHERE name = '张三';
COMMIT;
当A事务再次查询时候
SELECT money FROM account WHERE name = '张三'; -- 查到 500(发生了不可重复读)
幻读
幻读是指:同一个事务内,使用相同查询条件进行多次查询时,结果集的"行数"或"记录"发生了变化 ,原因是其他事务在期间插入或删除了"符合条件的新记录"。
Repeatable Read(可重复读)
举例
事务A开启
SELECT * FROM account WHERE money > 1000; -- 查到 2 条记录
事务B插入
INSERT INTO account(name, money) VALUES ('王五', 2000);
COMMIT;
当事务A再次执行相同查询
SELECT * FROM account WHERE money > 1000; -- 现在查到 3 条记录(多了一条"幻影")


事务隔离级别

默认自己安装的Mysql是可重复读 阿里云RDS是读已提交
查看事务隔离级别
SELECT @@transaction_isolation;
-- 或旧版本:
SELECT @@tx_isolation;
会话级别设置
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
全局设置
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;
可选级别
READ UNCOMMITTED 读未提交
READ COMMITTED 读已提交
REPEATABLE READ 可重复读
SERIALIZABLE 序列化读


Mysql进阶篇


存储引擎
在Java(编程语言)中,"引擎"(Engine)就是某一类核心功能的实现模块/组件,它封装了复杂的底层逻辑,开发者只需要调用接口就能使用。
MySQL 的存储引擎 是数据库内部用于管理表数据 的核心模块,负责实现数据的存储、索引的建立与维护、数据的读取、更新、删除等底层功能。存储引擎是基于表的,而不是基于库的,所以存储引擎也被称表类型
就像 Java 引擎对外暴露接口供你使用一样,MySQL 把数据管理的复杂逻辑封装在"引擎"里,不同引擎有不同的能力,比如是否支持事务、是否支持全文索引等。
-
决定数据怎么存(数据结构、磁盘布局)
-
决定索引怎么建(B+ 树、哈希等)
-
决定数据怎么读写(是否缓存、是否支持并发控制)
-
决定是否支持事务、外键、崩溃恢复等高级功能
在创建表时如果没有显式指定存储引擎(ENGINE),MySQL 会使用系统设置的"默认引擎",Mysql的默认引擎是InnoDB,并不是基于库的
default_storage_engine
是 MySQL 中用于指定"默认存储引擎"的系统变量 。
当你创建一张表时没有显式写 ENGINE=xxx
,就会自动使用这个变量指定的存储引擎。
查看默认引擎
SHOW VARIABLES LIKE 'default_storage_engine';
创建表时候指定存储引擎
CREATE TABLE 表名 (
字段1 字段1类型 COMMENT '字段1注释',
字段2 字段2类型 COMMENT '字段2注释',
...
字段n 字段n类型 COMMENT '字段n注释'
) ENGINE = INNODB COMMENT = '表注释';
MySQL 内置已"注册"的所有存储引擎类型
SHOW ENGINES;

InnoDB,MyISAM,MEMORY三种引擎介绍
InnoDB介绍
Mysql5.5之后,InnoDB是默认的Mysql存储引擎

InnoDB特点
DML操作遵循ACID模型,支持事务;
行级锁,提高并发访问性能
支持外键FOREIGN KEY约束,保证数据的完整性和正确性
每张使用 InnoDB 存储引擎的表,在磁盘上会有一个
.ibd
文件,这个文件就是该表的**"独立表空间"**,用来存储表的:
结构(配合
.sdi
文件)数据
索引
这个行为是由参数 innodb_file_per_table
控制的。
参数:innodb_file_per_table
SHOW VARIABLES LIKE 'innodb_file_per_table';


该文件是二进制的 普通记事本打开看不到啥

可以通过cmd命令
ibd2sdi emp.ibd ibd2sdi是命令 emp.ibd是表空间 好比表
我这里出现这个原因可能是文件损坏了 因为安装过不同版本的


换了一个库的表.ibd就好了



MyISAM介绍
MyISAM是Mysql早期的默认存储引擎
特点
不支持事务,不支持外键
支持表锁,不支持行锁
访问速度快(具体说和InnoDB比,看具体场景分析)
文件结构 .myd .myi .sdi
MEMORY介绍
Memory引擎的表数据存储在内存中,受到硬件问题,或者断电的影响,只能做为临时或者缓存使用
特点
内存中存放
hash索引(默认)
文件 xxx.sdi存储表结构信息无具体数据


主要用的是InnoDB MyISAM被MongoDB取代 MEMORY被Redis取代

Linux中Mysql安装
mkdir /usr/local/develop
cd /usr/local/develop
上传到服务器

rpm -ivh mysql84-community-release-el7-1.noarch.rpm

yum install -y mysql-community-server
systemctl start mysqld
systemctl status mysqld
查看初始密码
grep 'temporary password' /var/log/mysqld.log
mysql -u root -p
修改密码
ALTER USER 'root'@'localhost' IDENTIFIED BY 'NewPassword123!';

use mysql;
select host,user from user;

其实是建议再创建一个root Mysql支持同一个用户名有多个不同的登录规则
在Mysql中 用户是通过 user@host的组合来做唯一标识的而不是单纯看用户名
我是直接Update了 如果需要重新创建 执行下面三跳命令
CREATE USER 'root'@'%' IDENTIFIED BY 'StrongPassword123!';
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION;
FLUSH PRIVILEGES;这个好比刷新 好像不需要执行
UPDATE mysql.user SET host='%' WHERE user='root' AND host='localhost';
exit
vim /etc/my.cnf
bind-address = 0.0.0.0
systemctl restart mysqld
安全组打开3306
其他安装方式
你也可以下载整个RPM包
https://dev.mysql.com/downloads/mysql/

mkdir /usr/local/develop
cd /usr/local/develop
将下载的
mysql-8.4.4-1.el7.aarch64.rpm-bundle.tar 上传到服务器
tar -xzf mysql-8.4.4-1.el7.aarch64.rpm-bundle.tar
这种方式没有试验 使用的是上面的 解压之后 安装是有顺序的 大致如下
安装顺序

systemctl start mysqld
systemctl restart mysqld
systemctl stop mysqld
启动之后 查询密码
grep 'temporary password' /var/log/mysqld.log
mysql -u root -p
ALTER USER 'root'@'localhost' IDENTIFIED BY '1234';
如果需要的话


索引
索引是 MySQL 中用于提高数据查询效率的数据结构,它类似于书的目录或字典的检索页,通过有序的数据结构,快速定位到目标数据。
什么是索引:索引是用于提高数据查询效率有序的数据结构
没有索引的查询是全表查询:每条数据都需要查一遍
MySQL 的索引数据结构不是二叉树,而是多路搜索树------最常见的是 B+ 树。
以二叉树为例子介绍索引 注意 二叉树不是索引数据结构 就是说为什么快了


索引的好处是提高了查询,因索引需要维护,降低了增删改的效率
索引的好处是:提高查询效率
索引的代价是:增删改操作时要维护索引结构,导致效率下降

索引数据结构
Mysql的索引实在存储引擎层实现的,不同的存储引擎有不同的结构,主要包含以下几种

我们平常所说的索引,如果没有特别指明,都是指B+树结构的索引
注意二叉树不是索引的数据结构 下图讲的是为什么二叉树不作为索引数据结构的原因
红黑树面对大数据时候还是无法避免层级较深,导致检索速度慢

B-TREE
B+TREE


索引的分类

总结一句话: 主键索引一张表只能有一个 其他索引都可以有多个
MySQL 索引分为:主键索引(唯一 + 非空)、唯一索引(值不能重复)、普通索引(仅加速查询)、全文索引(文本匹配)。使用时根据数据特征和查询需求选择合适的类型。

创建索引
索引语法
CREATE [UNIQUE FULLTEXT] INDEX index_name ON table_name(列1,列2);
1.主键索引(PRIMARY KEY)
每个表只能有一个主键索引
主键值不能重复,不能为NULL(非空且唯一),实际是一个特殊的唯一索引
2.唯一索引(UNIQUE)
保证某一列或多列的值唯一
可以有多个唯一索引,允许NULL值(但只能有一个NULL)
CREATE UNIQUE INDEX inx_email ON users(email);
或者建表时
CREATE TABLE users (
id INT PRIMARY KEY,
email VARCHAR(100) UNIQUE,
username VARCHAR(100) UNIQUE
);
CREATE TABLE tasks (user_id INT,
project_id INT,
task_name VARCHAR(100),
UNIQUE KEY uniq_user_project (user_id, project_id)
);
3.普通索引
即B-TREE索引
普通索引,没有唯一性限制,用于提高查询速度
CREATE INDEX inx_name ON users(name);
普通索引(非唯一索引)是可以为 NULL 的!
4.复合索引
包含多个列的索引
只有查询条件命中从最左边开始的一段,才能用到索引
CREATE INDEX inx_name_age ON users(name,age);
5.全文索引(FULLTEXT)
用于全文搜索,主要针对CHAR VARCHAR TEXT
CREATE FULLTEXT INDEX idx_content ON articles(content)
MyISAM 和 InnoDB 引擎都支持 FULLTEXT(MySQL 5.6 及以上 InnoDB 开始支持)。
6.空间索引
用于地理空间数据(如点,多边形),依赖GIS
必须在使用MyISAM引擎时有效
CREATE SPATIAL INDEX idx_location ON places(location);
MySQL 复合索引的使用原则 ------ 特别是 最左前缀原则
例如创建了 复合索引或者多列联合唯一索引
例如 A列 B列 C列 D列
A B C是有索引的
只要查询条件中有A 都会使用到索引 即使查询条件中有D
查看索引
SHOW INDEX FROM table_name
删除索引
DROP INDEX index_name ON table_name;
注意:


练习
CREATE TABLE tb_user (
id INT PRIMARY KEY auto_increment COMMENT '主键',
NAME VARCHAR ( 50 ) NOT NULL COMMENT '用户名',
phone VARCHAR ( 11 ) NOT NULL COMMENT '手机号',
email VARCHAR ( 100 ) COMMENT '邮箱',
profession VARCHAR ( 11 ) COMMENT '专业',
age TINYINT UNSIGNED COMMENT '年龄',
gender CHAR ( 1 ) COMMENT '性别 , 1: 男, 2: 女',
STATUS CHAR ( 1 ) COMMENT '状态',
createtime datetime COMMENT '创建时间'
) COMMENT '系统用户表';
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES ('吕布', '17799990000', '[email protected]', '软件工程', 23, '1',
'6', '2001-02-02 00:00:00');
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES ('曹操', '17799990001', '[email protected]', '通讯工程', 33,
'1', '0', '2001-03-05 00:00:00');
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES ('赵云', '17799990002', '[email protected]', '英语', 34, '1',
'2', '2002-03-02 00:00:00');
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES ('孙悟空', '17799990003', '[email protected]', '工程造价', 54,
'1', '0', '2001-07-02 00:00:00');
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES ('花木兰', '17799990004', '[email protected]', '软件工程', 23,
'2', '1', '2001-04-22 00:00:00');
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES ('大乔', '17799990005', '[email protected]', '舞蹈', 22, '2',
'0', '2001-02-07 00:00:00');
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES ('露娜', '17799990006', '[email protected]', '应用数学', 24,
'2', '0', '2001-02-08 00:00:00');
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES ('程咬金', '17799990007', '[email protected]', '化工', 38,
'1', '5', '2001-05-23 00:00:00');
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES ('项羽', '17799990008', '[email protected]', '金属材料', 43,
'1', '0', '2001-09-18 00:00:00');
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES ('白起', '17799990009', '[email protected]', '机械工程及其自动
化', 27, '1', '2', '2001-08-16 00:00:00');
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES ('韩信', '17799990010', '[email protected]', '无机非金属材料工
程', 27, '1', '0', '2001-06-12 00:00:00');
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES ('荆轲', '17799990011', '[email protected]', '会计', 29, '1',
'0', '2001-05-11 00:00:00');
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES ('兰陵王', '17799990012', '[email protected]', '工程造价',
44, '1', '1', '2001-04-09 00:00:00');
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES ('狂铁', '17799990013', '[email protected]', '应用数学', 43,
'1', '2', '2001-04-10 00:00:00');
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES ('貂蝉', '17799990014', '[email protected]', '软件工程', 40,
'2', '3', '2001-02-12 00:00:00');
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES ('妲己', '17799990015', '[email protected]', '软件工程', 31,
'2', '0', '2001-01-30 00:00:00');
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES ('芈月', '17799990016', '[email protected]', '工业经济', 35,
'2', '0', '2000-05-03 00:00:00');
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES ('嬴政', '17799990017', '[email protected]', '化工', 38, '1',
'1', '2001-08-08 00:00:00');
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES ('狄仁杰', '17799990018', '[email protected]', '国际贸易',
30, '1', '0', '2007-03-12 00:00:00');
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES ('安琪拉', '17799990019', '[email protected]', '城市规划', 51,
'2', '0', '2001-08-15 00:00:00');
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES ('典韦', '17799990020', '[email protected]', '城市规划', 52,
'1', '2', '2000-04-12 00:00:00');
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES ('廉颇', '17799990021', '[email protected]', '土木工程', 19,
'1', '3', '2002-07-18 00:00:00');
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES ('后羿', '17799990022', '[email protected]', '城市园林', 20,
'1', '0', '2002-03-10 00:00:00');
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES ('姜子牙', '17799990023', '[email protected]', '工程造价', 29,
'1', '4', '2003-05-26 00:00:00');
1.name字段为姓名字段,该字段的值可能会重复,为该字段创建索引
CREATE INDEX inx_user_name ON tb_user(name);
2.phone手机号字段的值,是非空,且唯一的,为该字段创建唯一索引
CREATE UNIQUE INDEX inx_user_phone ON tb_user(phone);
3.为profession,age,status创建联合索引
CREATE INDEX inx_user_pro_age_sta ON tb_user(profession,age,status);
4.为email建立合适的索引来提升查询效率
CREATE INDEX inx_user_email ON tb_user(email);
SQL性能分析(SQL优化)
SQL执行频率
SHOW [SESSION|GLOBAL] STATUS查看服务状态信息
SHOW SESSION STATUS
SHOW GLOBAL STATUS
SHOW GLOBAL STATUS LIKE 'Com_______';
SHOW GLOBAL STATUS LIKE 'Com_select'
慢查询日志
慢查询日志记录了所有执行时间超过指定参数(long_query_time,单位:秒,默认10秒)的所有SQL语句的日志
Mysql的慢查询日志默认没有开启,需要在Mysql的配置文件(/etc/my.cnf)中配置




SHOW VARIABLES LIKE 'SLOW_QUERY_LOG'
这里用的是阿里云RDS 默认是开启的
没有开启是OFF

开启Profile详情
show profiles能够在做SQL优化时帮助我们了解时间都耗费到哪里了.通过have_profiling参数,可以查看当前Mysql是否支持profile操作
SELECT @@have_profiling
以下是阿里云RDS配置 默认是关闭的

SELECT @@profiling

可以通过SET 开启profiling
SET profiling=1
开启profile详情之后
通过指令可以查看执行耗时情况

开启profile之后,当你执行SQL操作 通过
SHOW profiles查看执行情况

SHOW profile for query 16; 查看指定sql各阶段耗时情况


Explain执行计划
开启profile可以看到SQL的执行时间,但是单纯看到执行时间还是不能精确定位分析SQL的优化
explain执行计划可以看到包括SELECT语句执行过程,例如如果连接和连接顺序,是否用到索引
通过EXPLAIN或者DESC命令获取Mysql如何执行SELECT语句信息
直接在SELECT语句之前加上关键字expain/desc
EXPLAN SELECT 字段列表 FROM 表名 WHERE 条件;


索引使用与失效
SELECT * FROM tb_sku WHERE sn = '10000003145004';
如果一张表的数据量很大 那么可以对sn创建索引
SELECT * FROM tb_sku WHERE sn = '10000003145004' AND other_field = 'xxx';
只要
sn
是查询条件的一部分,MySQL 仍然会使用sn
的索引来过滤第一步的结果集。
然后,它会在命中的 sn
结果基础上,继续判断其他字段(如 other_field
)的条件是否满足。
最左前缀法则
如果索引了多列(联合索引),要遵守最左前缀法则,最左前缀法则指的是查询从索引的最左列开始,并且不跳过索引中的列.如果跳跃某一列,索引将部分失效(后面的字段索引失效)
例如A列 B列 C列 D列 ABC是复合索引 D没有索引

注意只要WHERE条件中存在 和位置在前或者在后没关系
范围查询
联合索引中,出现范围查询,范围查询,右侧的列索引将失效

索引列运算
不要在索引列上进行运算操作,否则索引将失效

字符串不加引号
字符串类型字段使用时,不加引号,索引将失效
模糊查询
如果仅仅是尾部模糊匹配,索引不会失效.如果是头部模糊匹配,索引失效


OR连接条件
用OR分割开的条件,如果OR前的条件中的列有索引,而后面的列中没有索引,那么涉及的索引都不会被用到

索引使用原则
数据分布影响:如果Mysql评估使用索引比全表扫描慢,则不适用索引

当大部分需要查的时候也可能会走全表扫描
当查询的数据量占表中"大部分"时,MySQL 很可能放弃使用索引,选择 🔁全表扫描 ,因为它认为这样反而更快! 注意是可能
由查询优化器决定
主要由 MySQL 查询优化器(Query Optimizer) 根据多个因素动态评估查询成本,决定是否使用索引。
索引的选择(SQL提示)
当你创建了联合索引, 例如 A B C 三个字段 而又对A也创建了一个索引
当你用A字段进行查询时候
Mysql查询优化器会动态选择用哪一个索引
我们也可以指定使用哪个索引,称为SQL提示
简单来说,就是SQL语句中加入一些认为的提示来达到优化操作的目的
SQL提示
1.USE INDEX 告诉Mysql用哪个索引
例如:
SELECT * FROM tb_user USE INDEX(inx_use_pro) WHERE profession='软件工程';
2.IGNORE INDEX 告诉Mysql不要用哪个索引
SELECT * FROM tb_user IGNORE INDEX(inx_use_pro) WHERE profession='软件工程';
3.FORCE INDEX 告诉Mysql必须用这个索引
SELECT * FROM tb_user FORCE INDEX(inx_use_pro) WHERE profession='软件工程';
覆盖索引
尽量使用覆盖索引(查询使用了索引,并且需要返回的列,在该索引中已经全部能够找到),减少SELECT *



查询条件是索引并且返回的字段也是索引就是覆盖索引,使用SELECT * 容易出现回表查询 除非所有字段都创建了索引
前缀索引
当字段类型为字符串(VARCHAR,TEXT等)时,有时候需要索引很长的字符串.会使索引变得很大,影响查询效率.可以将字符串的一部分前缀,建立索引,可以节约索引空间.提高索引效率
前缀索引 :指的是 只对字符串字段的前 N 个字符建立索引,而不是整列。
创建前缀索引
CREATE INDEX inx_table_xxx ON table_name(CLOLUMN(n))
例如
CREATE INDEX idx_email_prefix ON users(email(10));
单例索引和联合索引的选择
单例索引:即一个索引只包含单个列
联合索引:即一个索引包含多个列

索引设计原则

SQL优化
创建索引可以当作查询数据的优化,以下介绍其他操作的优化
插入数据优化

在已经有sql脚本的情况下 可以使用load命令

主键优化
主键顺序插入的性能高于主键乱序插入----------->即为主键优化
order by优化


group by优化

limit优化
LIMIT查询时候问题出在哪里
SELECT * FROM tb_user LIMIT 0,10;
SELECT * FROM tb_user LIMIT 1000000000,10; 越往后查询越慢
一个非常常见且代价昂贵的问题是使用 LIMIT 2000000, 10
,这会导致 MySQL 必须扫描并排序 前 2000010 条记录 ,然后丢弃前 2000000 条,仅返回后 10 条。因此,随着 offset 越大,查询性能会指数级下降,排序和扫描成本非常高。
通过覆盖索引加子查询来提高效率

count优化
当数据量大的时候 使用InnoDB COUNT(*)查询是比较耗时的
暂时没有好的优化,如果非要优化,自己计数
例如使用Redis等内存级别数据库 每插入一条数据就+1 删除一条数据-1 比较繁琐

COUNT(*)效率最高 可以说约等于 COUNT(1)>COUNT(主键ID)>COUNT(其他字段)

update优化
INnoDB 三特性 事务 外键 行级锁
就是说更新时候通过索引更新效率高

❗不是"一定"加表锁,而是"可能"会加表锁 。
默认情况下,InnoDB 仍然尝试加 「行锁 」,即便没有命中索引,它会全表扫描并对每一行加行锁 。
但在某些情况下,为了提升性能或避免死锁冲突,可能自动退化成表锁(锁升级)。
视图/存储过程/触发器
视图
视图(View)是一种虚拟存在的表.视图中的数据并不在数据库中实际存在,行和列数据来自定义视图的查询中使用的表,并且是在使用视图时动态生成的。
视图(View)本质上就是一个被命名的查询语句
创建视图
CREATE [OR REPLACE] VIEW 视图名称 [(列名列表)]
AS SELECT语句
WITH \[CASCADED \| LOCAL\] CHECK OPTION

举例 SELECT后面查询的表称为基表
CREATE VIEW active_users AS
SELECT id, username FROM users WHERE status = 'active';

创建视图
CREATE VIEW stu_v_1 AS SELECT id,name FROM student WHERE id<=10;
查询视图
查看创建视图语句:SHOW CREATE VIEW 视图名称;
查看视图数据:SELECT * FROM 视图名称 ....;
修改视图
注意修改视图加上:OR REPLACE
CREATE OR REPLACE VIEW stu_v_1 AS
SELECT id, name, age
FROM student
WHERE id <= 20;
或者
ALTER VIEW stu_v_1 AS SELECT .......;
删除视图
DROP VIEW [IF EXISTS] 视图名称1 [,视图名2];
视图的创建增删改查
创建视图
CREATE OR REPLACE VIEW stu_v_1 AS SELECT id,name FROM student WHERE id<20;
查询视图中的数据
SELECT * FROM stu_v_1;
往视图中插入数据
INSERT INTO stu_v_1 VALUES(6,'Tom'); 注意插入的数据实际是插入到基表中的

视图的检查选项
当创建视图时候使用WITH CHECK OPTION子句创建时,Mysql会通过视图检查正在更改的每个,
新增,更新,删除是否符合视图定义.Mysql允许基于另一个视图创建视图.会检查视图规则
Mysql提供两个规则选项
1.CASCADED(默认)
例如
CREATE VIEW v1 AS SELECT id,name FROM student WHERE id<20;
如果后面不加
WITH [CASEADED|LOCAL] CHECK OPTION
这样在增删改时候视图是不会检查条件的 条件就是id<20
CREATE VIEW v2 AS SELECT id,name FROM v1 WHERE id<20 WITH CASEADED CHECK OPTION;
如果这样的话
在增删改时候视图会检查条件,不光检查V2条件 还会检查V1的条件,相当于在创建视图V1时候后面也加了WITH CASEADED CHECK OPTION;
2.LOCAL
WITH LOCAL CHECK OPTION的作用是如果视图本身有条件需要满足条件,不会强制父视图满足(如果有WITH [CASEADED|LOCAL ] CHECK OPTION需要满足,没有拉到)
