【Java WEB】MySQL从基础到高级

个人主页:Hello Code.
本文专栏:《Java WEB从入门到实战》
Java WEB完整内容请点击前往Java WEB从入门到实战 查看
如有问题,欢迎指正,一起学习~~



基本概念

数据库

  • 用于存储和管理数据的仓库
  • 英文单词为 DataBase。简称 DB
  • 它的存储空间很大,可以存放百万条、千万条、上亿条数据
  • 使用统一的方式操作数据库—SQL

Mysql

  • MySQL是一个最流行的关系型数据库管理系统之一。由瑞典 MySQL AB公司开发,后被 Oracle 公司收购
  • 关系型数据库是将数据保存在不同的数据表中,而不是将所有数据放在一个大仓库内,而且表与表之间还可以有关联关系。这样就提高了访问速度以及提高了灵活性
  • MySQL 所使用的SQL语句是用于访问数据库最常用的标准化语言
  • 免费(6版本之前)

安装

  • 为了能够更好的体现真实开发环境,将MySQL 安装在 Linux 系统上,以此来模拟公司的数据库服务器
  • 具体的安装步骤就不做说明了,网上很多
  • 在Windows下使用SQLyog连接数据库,下载安装方法网上也很多

DDL

数据库、数据表、数据的关系

在这里插入图片描述

  • MySQL服务器中可以创建多个数据库
  • 每个数据库可以包含多张数据表
  • 每个数据表中可以存储多条数据记录
  • 客户端通过数据库管理系统来操作MySQL数据库

SQL的介绍

  • SQL(Structured Query Language):结构化查询语言。其实就是定义了操作所有关系型数据库的一种规则
  • 通用语法规则
    SQL语句可以单行或者多行书写,以分号结尾
    可使用空格和缩进来增强语句的可读性
    MySQL 数据库的 SQL 语句不区分大小写,关键字建议使用大写
    单行注释:--注释内容 #注释内容(MySQL特有)
    多行注释:/*注释内容*/
  • SQL分类
    DDL(Data Definition Language):数据定义语言。用来操作数据库、表、列等
    DML(Data Manipulation Language):数据操作语言。用来对数据库中表的数据进行增删改
    DQL(Data Query Language):数据查询语言。用来查询数据库中表的记录(数据)
    DCL(Data Control Language):数据控制语言。用来定义数据库的访问权限和安全级别,以及创建用户

查询和创建数据库

  • 查询所有数据库
    SHOW DATABASES;
  • 查询数据库的创建语句
    SHOW CREATE DATABASE 数据库名称;
  • 创建数据库
    CREATE DATABASE 数据库名称;
  • 创建数据库(判断,如果不存在则创建)
    CREATE DATABASE IF NOT EXISTS 数据库名称;
  • 创建数据库(指定字符集)
    CREATE DATABASE 数据库名称 CHARACTER SET 字符集名称;
-- 查询所有数据库
SHOW DATABASES;

-- 查询数据库的创建语句
SHOW CREATE DATABASE mysql;

-- 创建数据库
CREATE DATABASE db1;

-- 创建数据库并判断,如果不存在则创建
CREATE DATABASE IF NOT EXISTS db2;

-- 创建数据库并指定字符集
CREATE DATABASE db3 CHARACTER SET utf8;

-- 查看数据库的字符集
SHOW CREATE DATABASE db3;


-- 练习:创建db4数据库、如果不存在则创建,并指定字符集为gbk
CREATE DATABASE IF NOT EXISTS db4 CHARACTER SET gbk;
-- 查看编码验证
SHOW CREATE DATABASE db4;

修改、删除、使用数据库

  • 修改数据库(修改字符集)
    ALTER DATABASE 数据库名称 CHARACTER SET 字符集名称;
  • 删除数据库
    DROP DATABASE 数据库名称;
  • 删除数据库(判断,如果存在则删除)
    DROP DATABASE IF EXISTS 数据库名称;
  • 使用数据库
    USE 数据库名称;
  • 查看当前使用的数据库
    SELECT DATABASE();
-- 修改数据库(字符集)
ALTER DATABASE db4 CHARACTER SET utf8;
-- 查看db4字符集
SHOW CREATE DATABASE db4;

-- 删除数据库
DROP DATABASE db1;

-- 删除数据库(判断,存在则删除)
DROP DATABASE IF EXISTS db2;

-- 使用数据库
USE db3;

-- 查看当前使用的数据库
SELECT DATABASE();

查询数据表

  • 查询所有的数据表
    SHOW TABLES;
  • 查询表结构
    DESC 表名;
  • 查询表字符集
    SHOW TABLE STATUS FROM 库名 LIKE '表名';
-- 使用mysql数据库
USE mysql;

-- 查询所有数据表
SHOW TABLES;

-- 查询表结构
DESC USER;

-- 查询表字符集
SHOW TABLE STATUS FROM mysql LIKE 'user';

创建数据表

创建数据表

CREATE TABLE 表名(
	列名(字段) 数据类型 约束,
    列名(字段) 数据类型 约束,
    ......
    列名(字段) 数据类型 约束
);

数据类型

  • int:整数类型
  • double:小数类型
  • date:日期类型。包含年月日,格式:yyyy-MM-dd
  • datetime:日期类型。包含年月日时分秒,格式:yyyy-MM-dd HH:mm:ss
  • timestamp:时间戳类型。包含年月日时分秒,格式yyyy-MM-dd HH:mm:ss
    如果不给该字段赋值、或赋值为null,则默认使用当前系统时间自动赋值
  • varchar(长度):字符串类型
-- 创建数据表
-- 创建一个product 商品类(商品编号、商品名称、商品价格、商品库存、上架时间)
CREATE TABLE product (
	id INT,
	NAME VARCHAR(20),
	price DOUBLE,
	stock INT,
	insert_time DATE
);
-- 查看product 表详细结构
DESC product;

数据表的修改

  • 修改表名
    ALTER TABLE 表名 RENAME TO 新表名;
  • 修改表的字符集
    ALTER TABLE 表名 CHARACTER SET 字符集名称;
  • 单独添加一列
    ALTER TABLE 表名 ADD 列名 数据类型;
  • 修改某列的数据类型
    ALTER TABLE 表名 MODIFY 列名 新数据类型;
  • 修改列名和数据类型
    ALTER TABLE 表名 CHANGE 列名 新列名 新数据类型;
  • 删除某一列
    ALTER TABLE 表名 DROP 列名
-- 修改表名
ALTER TABLE product RENAME TO product2;

-- 查看原字符集
SHOW TABLE STATUS FROM db3 LIKE 'product2';
-- 修改表的字符集
ALTER TABLE product2 CHARACTER SET gbk;

-- 给product2添加一列color
ALTER TABLE product2 ADD color VARCHAR(10);

-- 将color数据类型修改为int
ALTER TABLE product2 MODIFY color INT;
-- 查看表的详细结构
DESC product2;

-- 将color修改为address
ALTER TABLE product2 CHANGE color address VARCHAR(200);

-- 删除address列
ALTER TABLE product2 DROP address;

数据表的删除

  • 删除数据表
    DROP TABLE 表名;
  • 删除数据表(判断,如果存在则删除)
    DROP TABLE IF EXISTS 表名;
-- 删除product2表
DROP TABLE product2;

-- 删除表并判断
DROP TABLE IF EXISTS product2; 

DML

新增表数据

  • 给指定列添加数据
    INSERT INTO 表名(列名1,列名2,...)VALUES(值1,值2,...);
  • 给全部列添加数据
    INSERT INTO 表名 VALUES(值1,值2,...);
  • 批量添加数据
    INSERT INTO 表名(列名1,列名2,...)VALUES(值1,值2,...),(值1,值2,...),...;
    INSERT INTO 表名 VALUES(值1,值2,...),(值1,值2,...),...;

列名和值的数量以及数据类型要对应,除了数字类型,其他数据类型的数据都需要加引号(单引双引都行,推荐单引)。

-- 向product表添加一条数据(给全部列添加数据)
INSERT INTO product (id, NAME, price, stock, insert_time) VALUES (1, '手机', 2999, 20, '2022-02-26');
INSERT INTO product VALUES (2, '电脑', 3999, 36, '2022-02-27');

-- 向product表中添加指定数据
INSERT INTO product (id, NAME, price) VALUES (3, '电视', 1999.99);

-- 批量添加数据
INSERT INTO product VALUES (4, '冰箱', 999.99, 42, '2022-02-26'), (5, '空调', 1999, 23, '2030-01-01');

修改和删除表数据

  • 修改表中的数据
    UPDATE 表名 SET 列名1=值1,列名2=值2,...[WHERE条件];
    修改语句中必须加条件,如果不加条件,则会将所有数据都修改
  • 删除表中的数据
    DELETE FROM 表名 [WHERE 条件];
    删除语句中必须加条件,如果不加条件,则会将所有数据都删除
-- 修改手机价格为3500
UPDATE product SET price=3500 WHERE NAME='手机';

-- 修改电脑的价格为4800、库存为46
UPDATE product SET price=4800, stock=46 WHERE NAME='电脑';

-- 删除product 表中的空调信息
DELETE FROM product WHERE NAME='空调';
-- 删除product 表中库存为10的商品信息
DELETE FROM product WHERE stock=10;

DQL

查询语法

SELECT
	字段列表
FROM
	表名列表
WHERE
	条件列表
GROUP BY
	分组字段
HAVING
	分组后的过滤条件
ORDER BY
	排序
LIMIT
	分页

这些语句不一定全部出现,但要按照上面的顺序

查询全部

  • 查询全部的表数据
    SELECT * FROM 表名;
  • 查询指定字段的表数据
    SELECT 列名1,列名2,... FROM 表名;
  • 去除重复查询
    SELECT DISTINCT 列名1,列名2,... FROM 表名;
  • 计算列的值(四则运算)
    SELECT 列名1 运算符(+ - * /) 列名2 FROM 表名;

    如果某一列为null,可以进行替换IFNULL (表达式1,表达式2)
    表达式1:想替换的列
    表达式2:想替换的值

  • 起别名查询
    SELECT 列名 AS 别名 FROM 表名;
-- 准备数据(创建数据库以及表)
CREATE DATABASE db1;
USE db1;
CREATE TABLE product(
	id INT,			-- 商品编号
	NAME VARCHAR(20),	-- 商品名称
	price DOUBLE,		-- 商品价格
	brand VARCHAR(10),	-- 商品品牌
	stock INT,		-- 商品库存
	insert_time DATE 	-- 添加时间
);

-- 添加测试数据
INSERT INTO product VALUES
(1, '华为手机', 5999, '华为', 23, '2018-03-10'),
(2, '小米手机', 1999, '小米', 30, '2019-02-10'),
(3, '苹果手机', 3999, '苹果', 19, '2018-07-23'),
(4, '华为电脑', 4999, '华为', 14, '2020-10-27'),
(5, '小米电脑', 5996, '小米', 26, '2021-03-29'),
(6, '苹果电脑', 10000, '苹果', 15, '2022-02-26'),
(7, '联想电脑', 6999, '联想', NULL, '2023-03-14');


-- 查询全部的表数据
SELECT * FROM product;

-- 查询指定字段的表数据(name、price、brand)
SELECT NAME, price, brand FROM product;

-- 查询品牌
SELECT brand FROM product;
-- 查询品牌,去除重复
SELECT	DISTINCT brand FROM product;

-- 查询商品名称和库存,库存数量在原有的基础上加10
SELECT NAME,stock+10 FROM product;
-- 查询商品名称和库存,库存数量在原有的基础上加10,进行null值判断
SELECT NAME,IFNULL(stock,0)+10 FROM product;
-- 查询商品名称和库存,库存数量在原有的基础上加10,进行null值判断,起别名为getSum
SELECT NAME,IFNULL(stock,0)+10 AS getSum FROM product;
SELECT NAME,IFNULL(stock,0)+10 getSum FROM product;		-- 起别名时AS可以省略(空格隔开)

条件查询

  • 查询条件分类
符号 功能
> 大于
< 小于
>= 大于等于
<= 小于等于
= 等于
<> 或 != 不等于
BETWEEN…AND… 在某个范围之内(都包含)
IN(…) 多选…
LIKE 占位符 模糊查询;占位符_表示单个任意字符,%表示多个
IS NULL 是NULL
IS NOT NULL 不是 NULL
AND 或 && 并且
OR 或 || 或者
NOT 或 ! 非,不是
  • 条件查询语法
    SELECT 列名列表 FROM 表名 WHERE 条件;
-- 查询库存大于20的商品信息
SELECT * FROM product WHERE stock > 20;

-- 查询品牌为华为的商品信息
SELECT * FROM product WHERE brand = '华为';

-- 查询金额在4000-6000之间的商品信息
SELECT * FROM product WHERE price > 4000 AND price < 6000;
SELECT * FROM product WHERE price BETWEEN 4000 AND 6000;

-- 查询库存为14、30、23的商品信息
SELECT * FROM product WHERE stock = 14 OR stock = 30 OR stock = 23;
SELECT * FROM product WHERE stock IN (14, 30, 23);

-- 查询库存为null的商品信息
SELECT * FROM product WHERE stock IS NULL;

-- 查询库存不为null的商品信息
SELECT * FROM product WHERE stock IS NOT NULL;

-- 查询名称以小米为开头的商品信息
SELECT * FROM product WHERE NAME LIKE '小米%';

-- 查询名称第二个字是为的商品信息
SELECT * FROM product WHERE NAME LIKE '_为%';

-- 查询名称为四个字符的商品信息
SELECT * FROM product WHERE NAME LIKE '____';

-- 查询名称中包含电脑的商品信息
SELECT * FROM product WHERE NAME LIKE '%电脑%';

聚合函数查询

  • 聚合函数的介绍
    将一列数据作为一个整体,进行纵向的计算
  • 聚合函数分类
    函数名 功能
    COUNT(列名) 统计数量(一般选用不为null的列)
    MAX(列名) 最大值
    MIN(列名) 最小值
    SUM(列名) 求和
    AVG(列名) 平均值
  • 聚合函数查询语法
    SELECT 函数名(列名) FROM 表名 [WHERE 条件];
-- 计算product表中总记录条数
SELECT COUNT(*) FROM product;

-- 获取最高价格
SELECT MAX(price) FROM product;

-- 获取最低库存
SELECT MIN(stock) FROM product;

-- 获取总库存数量
SELECT SUM(stock) FROM product;

-- 获取品牌为苹果的总库存数量
SELECT SUM(stock) FROM product WHERE brand = '苹果';

-- 获取品牌为小米的平均商品价格
SELECT AVG(price) FROM product WHERE brand = '小米';

排序查询

排序查询语法
SELECT 列名列表 FROM 表名 [WHERE 条件] ORDER BY 列名 排序方式, 列名 排序方式, ...;

排序方式

  • ASC升序【默认】
  • DESC降序

如果有多个排序条件,只有当前面的条件值一样时,才会判断第二条件

-- 按照库存升序排序
SELECT * FROM product ORDER BY stock ASC;

-- 查询名称中包含手机的商品信息,按照金额降序排序
SELECT * FROM product WHERE NAME LIKE '%手机%' ORDER BY price DESC;

-- 按照金额升序排序,如果金额相同,按照库存降序排列
SELECT * FROM product ORDER BY price ASC, stock DESC;

分组查询

分组查询语法

SELECT 列名列表 FROM 表名 [WHERE 条件] GROUP BY 分组列名
[HAVING 分组后的条件过滤]
[ORDER BY 排序列名 排序方式]
-- 按照品牌分组,获取每组商品的总金额
SELECT brand, SUM(price) FROM product GROUP BY brand;

-- 对金额大于4000元的商品,按照品牌分组,获取每组商品的总金额
SELECT brand,SUM(price) FROM product WHERE price > 4000 GROUP BY brand;

-- 对金额大于4000元的商品,按照品牌分组,获取每组商品的总金额,只显示总金额大于7000元的
SELECT brand,SUM(price) AS getsum FROM product WHERE price > 4000 GROUP BY brand HAVING getsum > 7000;

-- 对金额大于4000元的商品,按照品牌分组,获取每组商品的总金额,只显示总金额大于7000元的,并按照总金额的降序进行排序
SELECT brand,SUM(price) AS getsum FROM product 
WHERE price > 4000 
GROUP BY brand 
HAVING getsum > 7000 
ORDER BY getsum DESC;

分页查询

分页查询语法

SELECT 列名列表 FROM 表名
[WHERE 条件]
[GROUP BY 分组列名]
[HAVING 分组后的条件过滤]
[ORDER BY 排序列名 排序方式]
LIMIT 当前页数,每页显示的条数;

当前页数 = (当前页数 - 1) * 每页显示的条数

# 每页显示3条数据

-- 第一页 当前页数 = (1 - 1) * 3
SELECT * FROM product LIMIT 0, 3;

-- 第二页 当前页数 = (2 - 1) * 3
SELECT * FROM product LIMIT 3, 3;

-- 第三页 当前页数 = (3 - 1) * 3
SELECT * FROM product LIMIT 6, 3;

LIMIT后的两个参数可以理解为:从几号索引开始,一页显示几个
故第一页:从0索引开始,显示3个(0,1,2)
第二页:从3索引开始,显示3个(3,4,5)
第三页:从6索引开始,显示3个(6,7,8)


约束

  • 什么是约束
    对表中的数据进行限定,保证数据的正确性、有效性、完整性
  • 约束的分类
    约束 作用
    PRIMARY KEY 主键约束
    PRIMARY KEY AUTO_INCREMENT 主键自增
    UNIQUE 唯一约束
    NOT NULL 非空约束
    FOREIGN KEY 外键约束
    FOREIGN KEY ON UPDATE CASCADE 外键级联更新
    FOREIGN KEY ON DELETE CASCADE 外键级联删除

主键约束

  • 特点
    主键约束默认包含非空和唯一两个功能
    一张表只能有一个主键
    主键一般用于表中数据的唯一标识
  • 建表时添加主键约束
    CREATE TABLE 表名(
    	列名 数据类型 PRIMARY KEY,
        ...
        列名 数据类型 约束
    );
    
  • 删除主键约束
    ALTER TABLE 表名 DROP PRIMARY KEY;
  • 建表以后单独添加主键
    ALTER TABLE 表名 MODIFY 列名 数据类型 PRIMARY KEY;
-- 创建学生表(编号、姓名、年龄) 编号为主键
CREATE TABLE students(
	id INT PRIMARY KEY,
	NAME VARCHAR(20),
	age INT
);

-- 查询学生表的详细信息
DESC students;

-- 添加数据
INSERT INTO students VALUES(NULL, '张三', 23);		-- 添加失败,主键不能为空
INSERT INTO students VALUES(1, '张三', 23);
INSERT INTO students VALUES(1, '李四', 24);		-- 添加失败,主键唯一
INSERT INTO students VALUES(2, '李四', 24);

-- 删除主键
ALTER TABLE students DROP PRIMARY KEY;

-- 建表后单独添加主键约束
ALTER TABLE students MODIFY id INT PRIMARY KEY;

主键自增约束

  • 建表时添加主键自增约束
    CREATE TABLE 表名(
    	列名 数据类型 PRIMARY KEY AUTO_INCREMENT,
        ...
        列名 数据类型 约束
    );
    

    添加自增约束之后,主键内容就可以写null,会自动进行加一操作

  • 删除主键自增约束
    ALTER TABLE 表名 MODIFY 列名 数据类型;
  • 建表后单独添加主键自增约束
    ALTER TABLE 表名 MODIFY 列名 数据类型 AUTO_INCREMENT;
    MySQL中的自增约束,必须配合主键的约束一起来使用!
-- 创建学生表(编号、姓名、年龄) 编号设为主键自增
CREATE TABLE students(
	id INT PRIMARY KEY AUTO_INCREMENT,
	NAME VARCHAR(20),
	age INT
);

-- 查询学生表的详细信息
DESC students;

-- 添加数据
INSERT INTO students VALUES(NULL, '张三',23);
INSERT INTO students VALUES(NULL, '李四',24);

-- 删除自增约束
ALTER TABLE students MODIFY id INT;		-- 只删除自增约束,不会删除主键约束

-- 建表后单独添加自增约束
ALTER TABLE students MODIFY id INT AUTO_INCREMENT;

唯一约束

  • 建表时添加唯一约束
    CREATE TABLE 表名(
    	列名 数据类型 UNIQUE,
        ...
        列名 数据类型 约束
    );
    
  • 删除唯一约束
    ALTER TABLE 表名 DROP INDEX 列名;
  • 建表后单独添加唯一约束
    ALTER TABLE 表名 MODIFY 列名 数据类型 UNIQUE;
-- 创建学生表(编号、姓名、年龄)  编号设为主键自增,年龄设为唯一
CREATE TABLE student(
	id INT PRIMARY KEY AUTO_INCREMENT,
	NAME VARCHAR(20),
	age INT UNIQUE
);
-- 查询学生表的详细信息
DESC student;

-- 添加数据
INSERT INTO student VALUES (NULL, '张三', 23);
INSERT INTO student VALUES (NULL, '李四', 23);

-- 删除唯一约束
ALTER TABLE student DROP INDEX age;

-- 建表后单独添加唯一约束
ALTER TABLE student MODIFY age INT UNIQUE;

非空约束

  • 建表时添加非空约束
    CREATE TABLE 表名(
    	列名 数据类型 NOT NULL,
        ...
        列名 数据类型 约束
    );
    
  • 删除非空约束
    ALTER TABLE 表名 MODIFY 列名 数据类型;
  • 建表后单独添加非空约束
    ALTER TABLE 表名 MODIFY 列名 数据类型 NOT NULL;
-- 创建学生表(编号、姓名、年龄)  编号设为主键自增,姓名设为非空,年龄设为唯一
CREATE TABLE student(
	id INT PRIMARY KEY AUTO_INCREMENT,
	NAME VARCHAR(20) NOT NULL,
	age INT UNIQUE
);

-- 查询学生表的详细信息
DESC student;

-- 添加数据
INSERT INTO student VALUES (NULL, '张三', 23);

-- 删除非空约束
ALTER TABLE student MODIFY NAME VARCHAR(20);

-- 添加非空约束
ALTER TABLE student MODIFY NAME VARCHAR(20) NOT NULL;

外键约束

  • 为什么要有外键约束?
    当表与表之间的数据有相关联性的时候,如果没有相关的数据约束,则无法保证数据的准确性!
    比如用户和订单,表与表之间也有关联
  • 外键约束的作用
    让表与表之间产生关联关系,从而保证数据的准确性!
  • 建表时添加外键约束
    CREATE TABLE 表名{
    	列名 数据类型 约束,
    	...
    	CONSTRAINT 外键名 FOREIGN KEY (本表外键列名) REFERENCES 主表名(主表主键列名)
    };
    
  • 删除外键约束
    ALTER TABLE 表名 DROP FOREIGN KEY 外键名;
  • 建表后单独添加外键约束
    ALTER TABLE 表名 ADD
    CONSTRAINT 外键名 FOREIGN KEY (本表外键列名) REFERENCES 主表名(主键列名);
    

    外键名一般取两个表的首字母_fk编号
    例如:ou_fk1

-- 建表时添加外键约束
-- 创建user表
CREATE TABLE USER(
	id INT PRIMARY KEY AUTO_INCREMENT,	-- id
	NAME VARCHAR(20) NOT NULL 		-- 姓名
);
-- 添加用户数据
INSERT INTO USER VALUES (NULL,'张三'),(NULL, '李四');

-- 创建orderlist订单表
CREATE TABLE orderlist(
	id INT PRIMARY KEY AUTO_INCREMENT,	-- id
	number VARCHAR(20) NOT NULL,		-- 订单编号
	uid INT, 				-- 外键列
	CONSTRAINT ou_fk1 FOREIGN KEY (uid) REFERENCES USER(id)
);

-- 添加订单数据
INSERT INTO orderlist VALUES (NULL, '001', 1),(NULL, '002', 1),
(NULL, '003', 2),(NULL, '004', 2);

-- 添加一个订单,但是没有真实用户,添加失败
INSERT INTO orderlist VALUES (NULL, '005', 3);

-- 删除李四用户,删除失败
DELETE FROM USER WHERE NAME='李四';

-- 删除外键约束
ALTER TABLE orderlist DROP FOREIGN KEY ou_fk1; 

-- 添加外键约束
ALTER TABLE orderlist ADD CONSTRAINT ou_fk1 FOREIGN KEY (uid) REFERENCES USER(id);

外键级联操作(了解)

  • 什么是级联更新
    当对主表中的数据进行修改时,从表中有关联的数据也会随之修改
  • 什么是级联删除
    当主表中的数据删除时,从表中的数据也会随之删除

级联操作在真实开发中很少使用,因为它耦合性太强,牵一发动全身

  • 添加级联更新
    ALTER TABLE 表名 ADD
    CONSTRAINT 外键名 FOREIGN KEY (本表外键列名) REFERENCES 主表名(主键列名)
    ON UPDATE CASCADE;
    
  • 添加级联删除
    ALTER TABLE 表名 ADD
    CONSTRAINT 外键名 FOREIGN KEY (本表外键列名) REFERENCES 主表名(主键列名)
    ON DELETE CASCADE;
    
  • 同时添加级联更新和级联删除
    ALTER TABLE 表名 ADD
    CONSTRAINT 外键名 FOREIGN KEY (本表外键列名) REFERENCES 主表名(主键列名)
    ON UPDATE CASCADE ON DELETE CASCADE;
    
-- 添加外键约束,同时添加级联更新和级联删除
ALTER TABLE orderlist ADD
CONSTRAINT ou_fk1 FOREIGN KEY (uid) REFERENCES USER(id)
ON UPDATE CASCADE ON DELETE CASCADE;

-- 将李四这个用户id修改为3,订单表中的uid也自动修改
UPDATE USER SET id=3 WHERE NAME='李四';

-- 将李四这个用户删除,订单表中的该用户所属的订单也自动修改
DELETE FROM USER WHERE id=3;

多表操作

  • 多表概念
    通俗的讲就是多张数据表,而表与表之间是可以有一定的关联关系,这种关联关系通过外键约束实现。
  • 多表的分类
    一对一
    一对多
    多对多

一对一

  • 适用场景
    例如人和身份证。一个人只有一个身份证,一个身份证只能对应一个人
  • 建表原则
    在任意一个表建立外键,去关联另外一个表的主键
-- 创建db5数据库
CREATE DATABASE db5;

-- 使用db5数据库
USE db5;

-- 创建person表
CREATE TABLE person(
	id INT PRIMARY KEY AUTO_INCREMENT,	-- 主键id
	NAME VARCHAR(20)			-- 姓名
);

-- 添加数据
INSERT INTO person VALUES (NULL, '张三'), (NULL, '李四');

-- 创建card表
CREATE TABLE card(
	id INT PRIMARY KEY AUTO_INCREMENT,		-- 主键id
	number VARCHAR(20) UNIQUE NOT NULL,		-- 身份证号
	pid INT UNIQUE,					-- 外键列
	CONSTRAINT cp_fk1 FOREIGN KEY (pid) REFERENCES person(id) 
);

-- 添加数据
INSERT INTO card VALUES (NULL, '12345', 1), (NULL, '56789', 2);

一对多

  • 适用场景
    用户和订单。一个用户可以有多个订单
    商品分类和商品。一个分类下可以有多个商品
  • 建表原则
    在多的一方,建立外键约束,来关联一的一方主键
-- 创建user表
CREATE TABLE USER(
	id INT PRIMARY KEY AUTO_INCREMENT,	-- 主键id
	NAME VARCHAR(20)			-- 姓名
);

-- 添加数据
INSERT INTO USER VALUES (NULL, '张三'), (NULL, '李四');

-- 创建orderlist表
CREATE TABLE orderlist(
	id INT PRIMARY KEY AUTO_INCREMENT,	-- 主键id
	number VARCHAR(20),			-- 订单编号
	uid INT,				-- 外键链
	CONSTRAINT ou_fk1 FOREIGN KEY (uid) REFERENCES USER(id)
);

-- 添加数据
INSERT INTO orderlist VALUES (NULL, '001', 1), (NULL, '002', 1), (NULL, '003', 2), (NULL, '004', 2);

多对多

  • 适用场景
    学生和课程。一个学生可以选择多个课程,一个课程也可以被多个学生选择
  • 建表原则
    需要借助第三张中间表,中间表至少包含两个列。这两个列作为中间表的外键,分别关联两张表的主键
-- 创建student表
CREATE TABLE student(
	id INT PRIMARY KEY AUTO_INCREMENT,	-- 主键id
	NAME VARCHAR(20)			-- 姓名
);

-- 添加数据
INSERT INTO student VALUES (NULL, '张三'), (NULL, '李四');

-- 创建course表
CREATE TABLE course(
	id INT PRIMARY KEY AUTO_INCREMENT,	-- 主键id
	NAME VARCHAR(10)			-- 课程名称
);

-- 添加数据
INSERT INTO course VALUES (NULL, '高数'), (NULL, '线代');

-- 创建中间表
CREATE TABLE stu_course(
	id INT PRIMARY KEY AUTO_INCREMENT,	-- 主键id
	sid INT,	-- 用于和student表中的id进行外键关联
	cid INT,	-- 用于和course表中的id进行外键关联
	CONSTRAINT sc_fk1 FOREIGN KEY (sid) REFERENCES student(id),	-- 添加外键约束
	CONSTRAINT sc_fk2 FOREIGN KEY (cid) REFERENCES course(id)	-- 添加外键约束
);

-- 添加数据
INSERT INTO stu_course VALUES (NULL, 1, 1), (NULL, 1, 2), (NULL, 2, 1), (NULL, 2, 2);

多表查询

多表查询分类

  • 内连接查询
  • 外连接查询
  • 子查询
  • 自关联查询

数据准备

-- 创建db4数据库
CREATE DATABASE db4;

-- 使用db4数据库
USE db4;

-- 创建user表
CREATE TABLE USER(
	id INT PRIMARY KEY AUTO_INCREMENT,	-- 用户id
	NAME VARCHAR(20),			-- 用户姓名
	age INT					-- 用户年龄
);

-- 添加数据
INSERT INTO USER VALUES (1, '张三', 23), (2, '李四', 24), (3, '王五', 25), (4, '赵六', 26);

-- 订单表
CREATE TABLE orderlist(
	id INT PRIMARY KEY AUTO_INCREMENT,	-- 订单id
	number VARCHAR(30),			-- 订单编号
	uid INT,				-- 外键字段
	CONSTRAINT ou_fk1 FOREIGN KEY (uid) REFERENCES USER(id) 
);

-- 添加数据
INSERT INTO orderlist VALUES (1, '001', 1);
INSERT INTO orderlist VALUES (2, '002', 1);
INSERT INTO orderlist VALUES (3, '003', 2);
INSERT INTO orderlist VALUES (4, '004', 2);
INSERT INTO orderlist VALUES (5, '005', 3);
INSERT INTO orderlist VALUES (6, '006', 3);
INSERT INTO orderlist VALUES (7, '007', NULL);

-- 商品分类表
CREATE TABLE category(
	id INT PRIMARY KEY AUTO_INCREMENT,	-- 商品分类id
	NAME VARCHAR(10)			-- 商品分类名称
);
-- 添加数据
INSERT INTO category VALUES (1, '手机数码'), (2, '电脑办公'), (3, '烟酒茶糖'), (4, '鞋靴箱包');

-- 商品表
CREATE TABLE product(
	id INT PRIMARY KEY AUTO_INCREMENT,	-- 商品id
	NAME VARCHAR(30),			-- 商品名称
	cid INT,				-- 外键字段
	CONSTRAINT cp_fk1 FOREIGN KEY (cid) REFERENCES category(id)
);

-- 添加数据
INSERT INTO product VALUES 
(1, '华为手机', 1), (2, '小米手机', 1), (3, '联想电脑', 2), 
(4, '苹果电脑', 2), (5, '中华香烟', 3), (6, '玉溪香烟', 3), (7, '计生用品', NULL);

-- 中间表
CREATE TABLE us_pro(
	upid INT PRIMARY KEY AUTO_INCREMENT,	-- 中间表id
	uid INT,				-- 外键字段,需要和用户表的主键产生关联
	pid INT,				-- 外键字段,需要和商品表的主键产生关联
	CONSTRAINT up_fk1 FOREIGN KEY (uid) REFERENCES USER(id),
	CONSTRAINT up_fk2 FOREIGN KEY (pid) REFERENCES product(id)
);

-- 添加数据
INSERT INTO us_pro VALUES
(NULL, 1, 1), (NULL, 1, 2), (NULL, 1, 3), (NULL, 1, 4),
(NULL, 1, 5), (NULL, 1, 6), (NULL, 1, 7), (NULL, 2, 1),
(NULL, 2, 2), (NULL, 2, 3), (NULL, 2, 4), (NULL, 2, 5),
(NULL, 2, 6), (NULL, 2, 7), (NULL, 3, 1), (NULL, 3, 2),
(NULL, 3, 3), (NULL, 3, 4), (NULL, 3, 5), (NULL, 3, 6),
(NULL, 3, 7), (NULL, 4, 1), (NULL, 4, 2), (NULL, 4, 3),
(NULL, 4, 4), (NULL, 4, 5), (NULL, 4, 6), (NULL, 4, 7);

内连接查询

  • 查询原理
    内连接查询的是两张表有交集的部分数据(有主外键关联的数据)
  • 查询语法
    显示内连接:SELECT 列名 FROM 表名1 [INNER] JOIN 表名2 ON 条件;
    隐式内连接:SELECT 列名 FROM 表名1,表名2 WHERE 条件;
/*
	显示内连接
*/

-- 查询用户信息和他对应的订单信息
SELECT * FROM USER INNER JOIN orderlist ON orderlist.uid = user.id;

-- 查询用户信息和对应的订单信息,起别名
SELECT * FROM USER u INNER JOIN orderlist o ON o.uid = u.id;

-- 查询用户姓名、年龄和订单号
SELECT
	u.name,		-- 用户姓名
	u.age,		-- 用户年龄
	o.number	-- 订单编号
FROM
	USER u 		-- 用户表
INNER JOIN
	orderlist o 	-- 订单表
ON
	o.uid = u.id;
	
/*
	隐式内连接
*/

-- 查询用户姓名,年龄,订单编号
SELECT
	u.name,		-- 用户姓名
	u.age,		-- 用户年龄
	o.number	-- 订单编号
FROM
	USER u,
	orderlist o
WHERE
	o.uid=u.id;

外连接查询

左外连接

  • 查询原理
    查询左表的全部数据,和左右两张表有交集部分的数据
  • 查询语法
    SELECT 列名 FROM 表名1 LEFT [OUTER] JOIN 表名2 ON 条件;

右外连接

  • 查询原理
    查询右表的全部数据,和左右两张表有交集部分的数据
  • 查询语法
    SELECT 列名 FROM 表名1 RIGHT [OUTER] JOIN 表名2 ON 条件;
-- 查询所有用户信息,以及用户对应的订单信息
SELECT
	u.*,
  	o.number
FROM
	USER u
LEFT OUTER JOIN
	orderlist o
ON
	u.id=o.uid;
  	
-- 所有订单信息,以及订单所属的用户信息
SELECT
	o.*,
  	u.name
FROM
  	USER u
RIGHT OUTER JOIN
  	orderlist o
ON
  	u.id=o.uid;

子查询

  • 概念
    查询语句中嵌套了查询语句,我们就将嵌套的查询称为子查询
  • 结果是单行单列的
    • 查询作用
      可以将查询的结果作为另一条语句的查询条件,使用运算符判断(=、>、>=、<、<=de等)
    • 查询语法
      SELECT 列名 FROM 表名 WHERE 列名=(SELECT 列名 FROM 表名 [WHERE 条件]);
  • 结果是多行单列的
    • 查询作用
      可以作为条件,使用运算符 IN 或 NOT IN 进行判断
    • 查询语法
      SELECT 列名 FROM 表名 WHERE 列名 [NOT] IN (SELECT 列名 FROM 表名 [WHERE 条件]);
  • 结果是多行多列的
    • 查询作用
      查询的结果可以作为一张虚拟表参与查询
    • 查询语法
      SELECT 列名 FROM 表名 [别名], (SELECT 列名 FROM 表名 [WHERE 条件] [别名] [WHERE 条件]);
-- 查询年龄最高的用户姓名
SELECT NAME,age FROM USER WHERE age=(SELECT MAX(age) FROM USER);

-- 查询张三和李四的订单信息
SELECT * FROM orderlist WHERE uid IN (SELECT id FROM USER WHERE NAME IN ('张三', '李四'));

-- 查询订单表中id大于4的订单信息和所属用户信息
SELECT
	o.number,
	u.name
FROM
	USER u
INNER JOIN
	(SELECT * FROM orderlist WHERE id > 4) o
ON
	u.id=o.uid;

自关联查询

  • 概念
    在同一张表中数据有关联性,我们可以把这张表当成多个表来查询
-- 创建员工表
CREATE TABLE employee(
	id INT PRIMARY KEY AUTO_INCREMENT,	-- 员工编号
	NAME VARCHAR(20),			-- 员工姓名
	mgr INT,				-- 上级编号
	salary DOUBLE				-- 员工工资
);

-- 添加数据
INSERT INTO employee VALUES (1001, '孙悟空', 1005, 9000.00),
(1002, '猪八戒', 1005, 8000.00),
(1003, '沙和尚', 1005, 8500.00),
(1004, '小白龙', 1005, 7900.00),
(1005, '唐僧', NULL, 15000.00),
(1006, '武松', 1009, 7600.00),
(1007, '李逵', 1009, 7400.00),
(1008, '林冲', 1009, 8100.00),
(1009, '宋江', NULL, 16000.00);

-- 查询所有员工的姓名及其直接上级的姓名,没有上级的员工也需要查询
SELECT
	e1.id,
	e1.name,
	e1.mgr,
	e2.id,
	e2.name
FROM
	employee e1
LEFT OUTER JOIN
	employee e2
ON
	e1.mgr=e2.id;

练习

  1. 查询用户的编号、姓名、年龄、订单编号
  2. 查询所有的用户。用户的编号、姓名、年龄、订单编号
  3. 查询所有的订单。用户的编号、姓名、年龄、订单编号
  4. 查询用户年龄大于23岁的信息。显示用户的编号、姓名、年龄、订单编号
  5. 查询张三和李四用户的信息。显示用户的编号、姓名、年龄、订单编号
  6. 查询商品分类的编号、分类名称、分类下的商品名称
  7. 查询所有的商品分类。商品分类的编号、分类名称、分类下的商品名称
  8. 查询所有的商品信息。商品分类的编号、分类名称、分类下的商品名称
  9. 查询所有的用户和所有的商品。显示用户的编号、姓名、年龄、商品名称
  10. 查询张三和李四这两个用户可以看到的商品。显示用户的编号、姓名、年龄、商品名称
-- 1.查询用户的编号、姓名、年龄、订单编号
SELECT 
	u.*,
	o.number 
FROM 
	USER AS u 
INNER JOIN 
	orderlist AS o 
ON 
	u.id=o.uid;

-- 2.查询所有的用户。用户的编号、姓名、年龄、订单编号
SELECT
	u.*,
	o.number
FROM
	USER u
LEFT OUTER JOIN
	orderlist o
ON
	u.id=o.uid;

-- 3.查询所有的订单。用户的编号、姓名、年龄、订单编号
SELECT
	u.*,
	o.number
FROM
	USER u
RIGHT OUTER JOIN
	orderlist o
ON
	u.id=o.uid;

-- 4.查询用户年龄大于23岁的信息。显示用户的编号、姓名、年龄、订单编号
# 方式1
SELECT
	u.*,
	o.number
FROM
	(SELECT * FROM USER WHERE age>23) u,
	orderlist o
WHERE
	u.id=o.uid;
# 方式2
SELECT
	u.*,
	o.number
FROM
	USER u,
	orderlist o
WHERE
	u.id=o.uid
	AND
	u.age > 23;


-- 5.查询张三和李四用户的信息。显示用户的编号、姓名、年龄、订单编号
# 方式1
SELECT
	u.*,
	o.number
FROM
	(SELECT * FROM USER WHERE NAME IN ('张三', '李四')) u,
	orderlist o
WHERE
	u.id=o.uid;
# 方式2
SELECT
	u.*,
	o.number
FROM
	USER u,
	orderlist o
WHERE
	u.id=o.uid
	AND
	u.name IN ('张三', '李四');
	
-- 6.查询商品分类的编号、分类名称、分类下的商品名称
# 方式1
SELECT
	c.id,
	c.name,
	p.name
FROM
	category c
INNER JOIN
	product p
ON
	c.id=p.cid;
# 方式2
SELECT
	c.*,
	p.name
FROM
	category c,
	product p
WHERE
	c.id=p.cid;

-- 7.查询所有的商品分类。商品分类的编号、分类名称、分类下的商品名称
SELECT
	c.*,
	p.name
FROM
	category c
LEFT OUTER JOIN
	product p
ON
	c.id=p.cid;

-- 8.查询所有的商品信息。商品分类的编号、分类名称、分类下的商品名称
SELECT
	c.*,
	p.name
FROM
	category c
RIGHT OUTER JOIN
	product p
ON
	c.id=p.cid;

-- 9.查询所有的用户和所有的商品。显示用户的编号、姓名、年龄、商品名称
SELECT
	u.*,
	p.name
FROM
	USER u,
	product p,
	us_pro up
WHERE
	up.uid=u.id
	AND
	up.pid=p.id;

-- 10.查询张三和李四这两个用户可以看到的商品。显示用户的编号、姓名、年龄、商品名称
SELECT
	u.id,
	u.name,
	u.age,
	p.name
FROM
	(SELECT * FROM USER WHERE NAME IN ('张三', '李四')) u,
	product p,
	us_pro up
WHERE
	up.uid=u.id
	AND
	up.pid=p.id;

视图

  • 视图:是一种虚拟存在的数据表,这个数据表并不在数据库中实际存在
  • 作用:将一些较为复杂的查询语句的结果,封装到一个虚拟表中,后期再有相同需求时,直接查询该虚拟表即可

数据准备

-- 创建db5数据库
CREATE DATABASE db5;

-- 使用db5数据库
USE db5;

-- 创建country表
CREATE TABLE country(
	id INT PRIMARY KEY AUTO_INCREMENT,	-- 国家id
	NAME VARCHAR(30)			-- 国家名称
);

-- 添加数据
INSERT INTO country VALUES (NULL, '中国'), (NULL, '美国'), (NULL, '俄罗斯');

-- 创建city表
CREATE TABLE city(
	id INT PRIMARY KEY AUTO_INCREMENT,	-- 城市id
	NAME VARCHAR(30),			-- 城市名称
	cid INT,				-- 外键列
	CONSTRAINT cc_fk1 FOREIGN KEY (cid) REFERENCES country(id)	-- 添加外键约束
);

-- 添加数据
INSERT INTO city VALUES (NULL, '北京', 1), (NULL, '上海', 1), (NULL, '纽约', 2), (NULL, '莫斯科', 3);

创建和查询

  • 创建视图语法
    CREATE VIEW 视图名称 [(列表列名)] AS 查询语句;
  • 查询视图语法
    SELECT * FROM 视图名称;
-- 创建city_country视图,保存城市和国家的信息(使用指定列名)
CREATE VIEW city_country (city_id, city_name, country_name) AS
SELECT
	c1.id,
	c1.name,
	c2.name
FROM
	city c1,
	country c2
WHERE
	c1.cid=c2.id;

-- 查询视图
SELECT * FROM city_country;

修改和删除

  • 修改视图数据语法
    UPDATE 视图名称 SET 列名=值 WHERE 条件;
  • 修改视图结构语法
    ALTER VIEW 视图名称 (列名列表) AS 查询语句;
  • 删除视图语法
    DROP VIEW [IF EXISTS] 视图名称;

注意:修改视图数据后,原表数据也会随之修改

SELECT * FROM city_country;
-- 修改视图数据,将北京修改为深圳
UPDATE city_country SET city_name='深圳' WHERE city_name='北京';

-- 将视图中的country_name修改为name
ALTER VIEW city_country (city_id, city_name, NAME) AS
SELECT
	c1.id,
	c1.name,
	c2.name
FROM
	city c1,
	country c2
WHERE
	c1.cid=c2.id;
	
-- 删除视图
DROP VIEW IF EXISTS city_country;

数据库备份和恢复

命令行方式

  • 备份
    登录到MySQL服务器,输入:mysqldump -u root -p 数据库名称 > 文件保存路径
    mysqldump -u root -p db5 > /root/db5.sql
    
  • 恢复
    1. 登录MySQL数据库:mysql -u root -p
    2. 删除已备份的数据库:DROP DATABASE db5;
    3. 重新创建名称相同的数据库:CREATE DATABASE db5;
    4. 使用该数据库:USE db5;
    5. 导入文件执行:source 备份文件全路径;

所谓的备份,就是将一些sql语句存储起来,恢复就是执行这些sql语句,重新创建数据库中的内容

图形化界面方式

这个方法比较简单,选中需要操作的数据库右键选择备份即可

恢复的时候删除原数据库再重新创建,然后选中新建的这个数据库,右键导入即可


存储过程和函数

  • 存储过程和函数是事先经过编译并存储在数据库中的一段SQL语句的集合
  • 存储过程和函数的好处
    • 提高代码的复用性
    • 减少数据在数据库和应用服务器之间的传输,提高效率
    • 减少代码层面的业务处理
  • 存储过程和函数的区别
    • 存储函数必须有返回值
    • 存储过程可以没有返回值

创建和调用

  • 创建存储过程
    -- 修改结束分隔符
    DELIMITER $
    
    -- 创建存储过程
    CREATE PROCEDURE 存储过程名称(参数列表)
    BEGIN
    	SQL 语句列表;
    END$
    
    -- 修改结束分隔符
    DELIMITER;
    
  • 调用存储过程
    CALL 存储过程名称(实际参数);
-- 数据准备

-- 创建db6数据库
CREATE DATABASE db6;

-- 使用db6数据库
USE db6;

-- 创建学生表
CREATE TABLE student(
	id INT PRIMARY KEY AUTO_INCREMENT,	-- 学生id
	NAME VARCHAR(20),			-- 学生姓名
	age INT,				-- 学生年龄
	gender VARCHAR(5),			-- 学生性别
	score INT 				-- 学生成绩
);

-- 添加数据
INSERT INTO student VALUES (NULL, '张三', 23, '男', 95), (NULL, '李四', 24, '男', 98), 
(NULL, '王五', 25, '女', 100), (NULL, '赵六', 26, '女', 90);


-- 按照性别进行分组,查询每组学生的总成绩。按照总成绩的升序排序
SELECT gender,SUM(score) getsum FROM student GROUP BY gender ORDER BY getsum ASC;
-- 创建stu_group() 存储过程,封装 分组查询总成绩,并按照总成绩升序排序的功能
DELIMITER $

CREATE PROCEDURE stu_group()
BEGIN
	SELECT gender,SUM(score) getsum FROM student GROUP BY gender ORDER BY getsum ASC;
END$

DELIMITER ;

-- 调用存储过程
CALL stu_group;

查看和删除

  • 查看数据库中所有的存储过程
    SELECT * FROM mysql.proc WHERE db='数据库名称';
  • 删除存储过程
    DROP PROCEDURE [IF EXISTS] 存储过程名称;
-- 查看db6数据库中所有的存储过程
SELECT * FROM mysql.proc WHERE db='db6';

-- 删除存储过程
DROP PROCEDURE IF EXISTS stu_group;

变量

  • 定义变量
    DECLARE 变量名 数据类型 [DEFAULT 默认值];
  • 变量赋值方式一
    SET 变量名=变量值;
  • 变量赋值方式二
    SELECT 列名 INTO 变量名 FROM 表名 [WHERE 条件];
-- 定义一个int类型变量,并赋默认值为10
DELIMITER $

CREATE PROCEDURE pro_test1()
BEGIN
	-- 定义变量
	DECLARE num INT DEFAULT 10;
	-- 使用变量
	SELECT num;
END$

DELIMITER ;

-- 调用pro_test1存储过程
CALL pro_test1();


/*
	变量赋值
*/

-- 定义一个varchar类型变量并赋值
DELIMITER $

CREATE PROCEDURE pro_test2()
BEGIN
	-- 定义变量
	DECLARE NAME VARCHAR(10);
	-- 为变量赋值
	SET NAME = '存储过程';
	-- 使用变量
	SELECT NAME;
END$

DELIMITER ;

-- 调用pro_test2存储过程
CALL pro_test2();


-- 定义两个int类型的变量,用于存储男女同学的总分数
DELIMITER $

CREATE PROCEDURE pro_test3()
BEGIN
	-- 定义两个变量
	DECLARE men,women INT;
	-- 为变量赋值
	SELECT SUM(score) INTO men FROM student WHERE gender = '男';
	SELECT SUM(score) INTO women FROM student WHERE gender = '女';
	-- 使用变量
	SELECT men,women;
END$

DELIMITER ;

-- 调用pro_test3
CALL pro_test3();

if语句

  • if语句标准语法
    IF 判断条件1 THEN 执行的sql语句1;
    [ELSEIF 判断条件2 THEN 执行的sql语句2;]
    ...
    [ELSE 执行的sql语句n;]
    END IF;
    
/*
	定义一个int类型的变量,用于存储班级总成绩
	定义一个varchar变量,用于存储分数描述
	根据总成绩判断:
		380分以上	学习优秀
		320~380		学习不错
		320以下		学习一般
*/

DELIMITER $

CREATE PROCEDURE pro_test4()
BEGIN
	-- 定义变量
	DECLARE total INT;
	DECLARE info VARCHAR(10);
	-- 查询班级总成绩为total赋值
	SELECT SUM(score) INTO total FROM student;
	-- 判断
	IF total >= 380 THEN
		SET info = '学习优秀';
	ELSEIF total BETWEEN 320 AND 380 THEN 
		SET info = '学习不错';
	ELSE
		SET info = '学习一般';
	END IF;
	-- 使用变量
	SELECT total,info;
END$

DELIMITER ;

-- 调用pro_test4
CALL pro_test4();

参数传递

  • 存储过程的参数和返回值
    CREATE PROCEDURE 存储过程名称([IN][OUT][INOUT] 参数名 数据类型)
    BEGIN
    	SQL语句列表;
    END$
    

    IN:代表输入参数,需要由调用者传递实际数据(默认)
    OUT:代表输出参数,该参数可以作为返回值
    INOUT:代表既可以作为输入参数,也可以作为输出参数

/*
	输入总成绩变量,代表学生总成绩
	输出分数描述变量,代表学生总成绩的描述信息
	根据总成绩判断
		380分及以上	学习优秀
		320~380		学习不错
		320分及以下	学习一般
*/

DELIMITER $

CREATE PROCEDURE pro_test5(IN total INT,OUT info VARCHAR(10))
BEGIN
	-- 对总成绩判断
	IF total >= 380 THEN
		SET info = '学习优秀';
	ELSEIF total BETWEEN 320 AND 380 THEN
		SET info = '学习不错';
	ELSE
		SET INFO = '学习一般';
	END IF;
END$

DELIMITER ;

-- 调用存储过程
CALL pro_test5(383, @info);

CALL pro_test5((SELECT SUM(score) FROM student), @info);

SELECT @info;

while循环

  • while 循环语法
    初始化语句;
    WHILE 条件判断语句 DO
    	循环体语句;
    	条件控制语句;
    END WHILE;
    
-- 计算1~100之间的偶数和

DELIMITER $

CREATE PROCEDURE pro_test6()
BEGIN
	-- 定义求和变量
	DECLARE result INT DEFAULT 0;
	-- 定义初始化变量
	DECLARE num INT DEFAULT 1;
	-- while 循环
	WHILE num <= 100 DO
		IF num % 2 = 0 THEN
			SET result = result + num;
		END IF;
		
		SET num = num + 1;
	END WHILE;
	-- 查询求和结果
	SELECT result;
END$

DELIMITER ;

-- 调用存储过程
CALL pro_test6();

存储函数

  • 存储函数和存储过程是非常相似的,区别在于存储函数必须有返回值
  • 创建存储函数
    CREATE FUNCTION 函数名称(参数列表)
    RETURNS 返回值类型
    BEGIN
    		SQL语句列表;
    		RETURN 结果;
    END$
    
  • 调用存储函数
    SELECT 函数名称(实际参数);
  • 删除函数
    DROP FUNCTION 函数名称;
-- 定义存储函数,获取学生表中成绩大于95分的学生数量
DELIMITER $

CREATE FUNCTION fun_test()
RETURNS INT
BEGIN
	-- 定义变量
	DECLARE s_count INT;
	-- 查询数量并赋值
	SELECT COUNT(*) INTO s_count FROM student WHERE score > 95;
	-- 返回
	RETURN s_count;
END$

DELIMITER ;

-- 调用函数
SELECT fun_test();

-- 删除函数
DROP FUNCTION fun_test;

触发器

  • 触发器是与表有关的数据库对象,可以在insert、update、delete 之前或之后触发并执行触发器中定义的SQL语句
  • 这种特性可以协助应用系统在数据库端确保数据的完整性、日志记录、数据校验等操作
  • 可以使用别名 NEW 或者 OLD来引用触发器中发生变化的内容记录
  • 触发器分类
触发器类型 OLD NEW
INSERT 型触发器 无(因为插入前无数据) NEW表示将要或者已经新增的数据
UPDATE 型触发器 OLD表示修改之前的数据 NEW表示将要或已经修改后的数据
DELETE 型触发器 OLD表示将要或者已经删除的数据 无(因为删除后状态无数据)

触发器的操作

  • 创建触发器
    DELIMITER $
    
    CREATE TRIGGER 触发器名称
    BEFORE|AFTER INSERT|UPDATE|DELETE
    ON 表名
    FOR EACH ROW
    BEGIN
    	触发器要执行的功能
    END$
    
    DELIMITER ;
    
  • 数据准备
    -- 创建db7数据库
    CREATE DATABASE db7;
    
    -- 使用db7数据库
    USE db7;
    
    -- 创建账户表account
    CREATE TABLE account(
    	id INT PRIMARY KEY AUTO_INCREMENT,	-- 账户id
    	NAME VARCHAR(20),			-- 姓名
    	money DOUBLE				-- 余额
    );
    
    -- 添加数据
    INSERT INTO account VALUES (NULL, '张三', 1000), (NULL, '李四', 1000);
    
    -- 创建日志表account_log
    CREATE TABLE account_log(
    	id INT PRIMARY KEY AUTO_INCREMENT,	-- 日志id
    	operation VARCHAR(20),			-- 操作类型 (insert update delete)
    	operation_time DATETIME,		-- 操作时间
    	operation_id INT,			-- 操作表的id
    	operation_param VARCHAR(200)		-- 操作参数
    );
    
  • INSERT 型触发器
    -- 创建insert型触发器,用于对account表新增数据进行日志的记录
    DELIMITER $
    
    CREATE TRIGGER account_insert
    AFTER INSERT
    ON account
    FOR EACH ROW
    BEGIN
    	INSERT INTO account_log VALUES (NULL, 'INSERT', NOW(), new.id, CONCAT('插入后{id=',new.id,',name=',new.name,',money=',new.money,'}'));
    END$
    
    DELIMITER ;
    
    -- 向account表添加一条数据
    INSERT INTO account VALUES (NULL, '王五', 2000);
    
    -- 查询account表
    SELECT * FROM account;
    
    -- 查询account_log表
    SELECT * FROM account_log;
    
  • UPDATE 型触发器
    -- 创建update型触发器,用于对account表修改数据进行日志记录
    DELIMITER $
    
    CREATE TRIGGER account_update
    AFTER UPDATE
    ON account
    FOR EACH ROW
    BEGIN
    	INSERT INTO account_log VALUES (NULL, 'UPDATE', NOW(), new.id, CONCAT('更新前{id=',old.id,',name=',old.name,',money=',old.money,'}','更新后{id=',new.id,',name=',new.name,',money=',new.money,'}'));
    END$
    
    DELIMITER ;
    
    -- 修改account表中李四的金额为2000
    UPDATE account SET money = 2000 WHERE NAME = '李四';
    
    -- 查询account表
    SELECT * FROM account;
    
    -- 查询account_log表
    SELECT * FROM account_log;
    
  • DELETE 型触发器
    -- 创建delete型触发器,用于对account表删除的数据进行日志的记录
    DELIMITER $
    
    CREATE TRIGGER account_delete
    AFTER DELETE
    ON account
    FOR EACH ROW
    BEGIN
    	INSERT INTO account_log VALUES (NULL, 'DELETE', NOW(), old.id, CONCAT('删除前{id=',old.id,',name=',old.name,',money=',old.money,'}'));
    END$
    
    DELIMITER ;
    
    -- 删除account表中王五
    DELETE FROM account WHERE NAME = '王五';
    
    -- 查询account表
    SELECT * FROM account;
    
    -- 查询account_log表
    SELECT * FROM account_log;
    

查看和删除

  • 查看触发器
    SHOW TRIGGERS;
  • 删除触发器
    DROP TRIGGER 触发器名称;
-- 查看触发器
SHOW TRIGGERS;

-- 删除account_delete触发器
DROP TRIGGER account_delete;

事务

事务:一条或多条SQL语句组成一个执行单元,其特点是这个单元要么同时成功要么同时失败
单元中的每条SQL语句都相互依赖,形成一个整体
如果某条SQL语句失败或者出现错误,那么这个单元就会撤回到事务最初的状态
如果单元中所有的SQL语句都执行成功,则事务就顺利执行

基本使用

  • 开启事务
    START TRANSACTION;
  • 回滚事务
    ROLLBACK;
  • 提交事务
    COMMIT;
-- 创建db8数据库
CREATE DATABASE db8;

-- 使用db8数据库
USE db8;

-- 创建账户表
CREATE TABLE account(
	id INT PRIMARY KEY AUTO_INCREMENT,	-- 账户id
	NAME VARCHAR(20),			-- 账户名称
	money DOUBLE				-- 账户余额
);
-- 添加数据
INSERT INTO account VALUES (NULL, '张三', 1000), (NULL, '李四', 1000);
-- 张三给李四转账500元

-- 开启事务
START TRANSACTION;

-- 1. 张三账户-500
UPDATE account SET money = money - 500 WHERE NAME = '张三';

出错了...

-- 2.李四账户+500
UPDATE account SET money = money + 500 WHERE NAME = '李四';

-- 回滚事务
ROLLBACK;

-- 提交事务
COMMIT;

提交方式

  • 事务提交方式
    • 自动提交(MySQL默认)
    • 手动提交
  • 查看事务提交方式
    SELECT @@AUTOCOMMIT;

    0代表手动提交
    1代表自动提交

  • 修改事务提交方式
    SET @@AUTOCOMMIT = 数字;
-- 查询事务提交方式
SELECT @@autocommit;

-- 修改事务提交方式
SET @@autocommit = 0;

UPDATE account SET money = 2000 WHERE id = 1;  -- 临时修改,并未提交

COMMIT;

四大特征(ACID)

  • 原子性(Atomicty)
    原子性指事物包含的所有操作要么全部成功,要么全部失败回滚
    因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响
  • 一致性(Consistency)
    一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态
    也就是说一个事务执行之前和执行之后都必须处于一致性状态
  • 隔离性(Isolcation)
    隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务
    不能被其他事务的操作所干扰,多个并发事务之间要相互隔离
  • 持久性(Durability)
    持久性是指一个事务一旦提交了,那么对数据库中的数据的改变就是永久性的
    即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作

隔离级别

  • 事务的隔离级别
    多个客户端操作时,各个客户端的事务之间应该是隔离的,相互独立的,不受影响的
    而如果多个事务操作同一批数据时,就会产生不同的问题,我们需要设置不同的隔离级别来解决这些问题
  • 隔离级别分类
    隔离级别 名称 会引发的问题
    read uncommitted 读未提交 脏读、不可重复读、幻读
    read committed 读已提交 不可重复读、幻读
    repeatable read 可重复读 幻读
    serializable 串行化
  • 引发的问题
    问题 现象
    脏读 在一个事务处理过程中读取到了另一个未提交事务中的数据,导致两次查询结果不一致
    不可重复读 在一个事务处理过程中读取到了另一个事务中修改并已提交的数据,导致两次查询不一致
    幻读 查询某数据不存在,准备插入此纪录,但执行插入时发现此纪录已存在,无法插入。或者查询数据不存在执行删除操作,却发现删除成功
  • 查询数据库隔离级别
    SELECT @@TX_ISOLATION;
  • 修改数据库隔离级别
    SET GLOBAL TRANSACTION ISOLATION LEVEL 级别字符串;
-- 查询事务隔离级别
SELECT @@tx_isolation;

-- 修改事务隔离级别(修改后需要重新连接)
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;

总结

序号 隔离级别 名称 脏读 不可重复读 幻读 数据库默认隔离级别
1 read uncommitted 读未提交
2 read committed 读已提交 Oracle
3 repeatable read 可重复读 MySQL
4 serializable 串行化

注意 :隔离级别从小到大安全性越来越高,但是效率越来越低,所以不建议修改数据库默认的隔离级别


存储引擎

在这里插入图片描述

体系结构

  • 客户端连接
    支持接口:支持的客户端连接,例如:C、Java、PHP等语言来连接MySQL数据库
  • 第一层:网络连接层
    连接池:管理、缓冲用户的连接,线程处理等需要缓存的需求
  • 第二层:核心服务层
    • 管理服务和工具:系统的管理和控制工具,例如备份恢复、复制、集群等
    • SQL接口:接受SQL命令,并且返回查询结果
    • 查询解析器:验证和解析SQL命令,例如过滤条件、语法结构等
    • 缓存:如果缓存当中有想查询的数据,则直接将缓存中的数据返回。没有的话重新查询
  • 第三层:存储引擎层
    插件式存储引擎:管理和操作数据的一种机制,包括(存储数据、如何更新、查询数据等)
  • 第四层:系统文件层
    文件系统:配置文件、数据文件、日志文件、错误文件、二进制文件等等的保存

存储引擎

  • 在生活中,引擎就是整个机器运行的核心(发动机),不同的引擎具备不同的功能,应用于不同的场景之中
  • MySQL数据库使用不同的机制存取表文件,包括存储方式、索引技巧、锁定水平等不同的功能。这些不同的技术以及配套的功能称为存储索引
  • Oracle、SQL server等数据库只有一种存储引擎。而MySQL针对不同的需求,配置不同的存储索引,就会让数据库采取不同处理数据的方式和扩展功能
  • MySQL支持的存储引擎有很多,常用的有三种:InnoDB、MyISAM、MEMORY
  • 特性对比
    • MyISAM存储引擎:访问快,不支持事务和外键操作
    • InnoDB存储引擎:支持事务和外键操作,支持并发控制,占用磁盘空间大(MySQL 5.5版本后默认)
    • MEMORY存储引擎:内存存储,速度快,不安全。适合小量快速访问的数据

基本操作

  • 查询数据库支持的存储引擎
    SHOW ENGINES;
  • 查询某个数据库中所有数据表的存储引擎
    SHOW TABLE STATUS FROM 数据库名称;
  • 查询某个数据库中某个数据表的存储索引
    SHOW TABLE STATUS FROM 数据库名称 WHERE NAME = '数据表名称';
  • 创建数据表,指定存储引擎
    CREATE TABLE 表名(
    	列名 数据类型,
        ...
    )ENGINE = 引擎名称;
    
  • 修改数据表的存储引擎
    ALTER TABLE 表名 ENGINE = 引擎名称;
-- 查询数据库支持的存储引擎
SHOW ENGINES;

-- 查询db4数据库所有表的存储引擎
SHOW TABLE STATUS FROM db4;

-- 查看db4数据库中category表的存储引擎
SHOW TABLE STATUS FROM db4 WHERE NAME = 'category';

-- 创建数据表并指定存储引擎
CREATE TABLE engine_test(
	id INT PRIMARY KEY AUTO_INCREMENT,
	NAME VARCHAR(10)
)ENGINE = MYISAM;

SHOW TABLE STATUS FROM db4 WHERE NAME = 'engine_test';

-- 修改数据表的存储引擎
ALTER TABLE engine_test ENGINE = INNODB;

存储引擎的选择

  • MyISAM
    • 特点:不支持事务和外键操作。读取速度快,节约资源
    • 使用场景:以查询操作为主,只有很少的更新和删除操作,并且对事务的完整性、并发性要求不是很高
  • InnoDB
    • 特点:MySQL的默认存储引擎,支持事务和外键操作
    • 使用场景:对事务的完整性有比较高的要求,在并发条件下要求数据的一致性,读写频繁的操作
  • MEMORY
    • 特点:将所有数据保存在内存中,在需要快速定位记录和其他类似数据环境下,可以提供更快的访问
    • 使用场景:通常用于更新不太频繁的小表,用来快速得到访问的结果

总结 :针对不同的需求场景,来选择最适合的存储引擎即可。如果不确定,则使用默认的存储引擎


索引

  • MySQL索引:是帮助MySQL高效获取数据的一种数据结构。所以,索引的本质就是数据结构
  • 在表数据之外,数据系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式指向数据,这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引

分类

  • 按照功能分类
    • 普通索引:最基本的索引,没有任何限制
    • 唯一索引:索引列的值必须唯一,但允许有空值。如果是组合索引,则列值组合必须唯一
    • 主键索引:一种特殊的唯一索引,不允许有空值。在建表时有主键列同时创建主键索引
    • 联合索引:顾名思义,就是将单列索引进行组合
    • 外键索引:只有InnoDB引擎支持外键索引,用来保证数据的一致性、完整性和实现级联操作
    • 全文索引:快速匹配全部文档的方式。InnoDB引擎5.6版本后才支持全文索引。MEMORY引擎不支持
  • 按照结构分类
    • BTree索引:MySQL使用最频繁的一个索引数据结构,是InnoDB和MyISAM存储索引默认的索引类型,底层是基于B+Tree数据结构
    • Hash索引:MySQL中Memory存储引擎默认支持的索引类型

创建和查询

  • 创建索引
    CREATE [UNIQUE | FULLTEXT] INDEX 索引名称
    [USING 索引类型] 	-- 默认是BTREE
    ON 表名(列名...);
    
  • 查看索引
    SHOW INDEX FROM 表名;
-- 创建db9数据库
CREATE DATABASE db9;

-- 使用db9数据库
USE db9;

-- 创建student表
CREATE TABLE student(
	id INT PRIMARY KEY AUTO_INCREMENT,
	NAME VARCHAR(10),
	age INT,
	score INT
);

-- 添加数据
INSERT INTO student VALUES (NULL, '张三', 23, 98), (NULL, '李四',24, 95),
(NULL, '王五',25, 96), (NULL, '赵六',26, 94), (NULL, '周七',27, 99);
-- 为student表中的name列创建一个普通索引
CREATE INDEX idx_name ON student(NAME);

-- 为student表中的age列创建一个唯一索引
CREATE UNIQUE INDEX idx_age ON student(age);

-- 查询索引(主键列自带主键索引)
SHOW INDEX FROM student;

-- 查询db4中的product表(外键列自带外键索引)
SHOW INDEX FROM product;

添加和删除

  • 添加索引
    • 普通索引:ALTER TABLE 表名 ADD INDEX 索引名称(列名);
    • 组合索引:ALTER TABLE 表名 ADD INDEX 索引名称(列名1,列名2,...);
    • 主键索引:ALTER TABLE 表名 ADD PRIMARY KEY(主键列名);
    • 外键索引:ALTER TABLE 表名 ADD CONSTRAINT 外键名 FOREIGN KEY (本表外键列名) REFERENCES 主表名(主键列名);
    • 唯一索引:ALTER TABLE 表名 ADD UNIQUE 索引名称(列名);
    • 全文索引:ALTER TABLE 表名 ADD FULLTEXT 索引名称(列名);
  • 删除索引
    DROP INDEX 索引名称 ON 表名;
-- 为student表中score列添加唯一索引
ALTER TABLE student ADD UNIQUE idx_score(score);

-- 查询student表的索引
SHOW INDEX FROM student;

-- 删除索引
DROP INDEX idx_score ON student;

索引的原理

  • 索引是在存储引擎中实现的,不同的存储引擎所支持的索引也不一样,这里主要介绍InnoDB 引擎的BTree索引
  • BTree索引类型是基于B+Tree数据结构的,而B+Tree数据结构又是BTree数据结构的变种。通常使用在数据库和操作系统中的文件系统,特点是能够保持数据稳定有序
  • 需要理解的
    • 磁盘存储
    • BTree
    • B+Tree

磁盘存储

  • 系统从磁盘读取数据到内存时是以磁盘块(block)为基本单位的
  • 位于同一个磁盘块中的数据会被一次性读取出来,而不是需要什么取什么
  • InnoDB 存储引擎中有页(page)的概念,页是其磁盘管理的最小单位。InnoDB存储引擎中默认每个页的大小为 16 KB
  • InnoDB 引擎将若干个地址连接磁盘块,以此来达到页的大小 16 KB,在查询数据时如果一个页中的每条数据都能有助于定位数据记录的位置,这将会减少磁盘 I/O 次数,提高查询效率

BTree

在这里插入图片描述

  • 在每一个结点上,除了保存键值外,还会保存真实的数据
  • 左边存小的,右边存大的,类似二叉树
  • 但是在查询数据时,涉及到的磁盘块数据都会被读取出来,会增加查询数据时磁盘的IO次数,效率并不高

B+Tree

在这里插入图片描述

  • 在每一个结点上,只保存键值,并不保存真实的数据
  • 在叶子结点上保存着真实数据
  • 所有的叶子结点之间都有连接指针
  • 好处
    • 提高查询速度
    • 减少磁盘的IO次数
    • 树型结构较小

设计原则

  • 创建索引遵循的原则
    1. 对查询频次较高,且数据量比较大的表建立索引
    2. 使用唯一索引,区分度越高,使用索引的效率越高
    3. 索引字段的选择,最佳候选应当从where子句的条件中提取
    4. 索引虽然可以有效的提升查询数据的效率,但并不是多多益善
  • 最左匹配原则(使用组合索引)
    • 例如:为user表中的name、address、phone 列添加组合索引
      ALTER TABLE user ADD INDEX idx_three(name,address,phone);
    • 此时,组合索引idx_three实际建立了(name)、(name,address)、(name,address,phone)三个索引
    • 下面的三个SQL语句都可以命中索引
      SELECT * FROM user WHERE address = ‘北京’ AND phone = '12345' AND name = '张三';
      SELECT * FROM user WHERE name = '张三' AND address = '北京';
      SELECT * FROM user WHERE name = '张三';
    • 这三条SQL语句在检索时分别会使用以下索引进行数据匹配
      (name,address,phone)
      (name,address)
      (name)
    • 索引字段出现的顺序可以是任意的,MySQL 优化器会帮我们自动的调整where 条件中的顺序
    • 如果组合索引中最左边的列不在查询条件中,则不会命中索引
      SELECT * FROM user WHERE address = '北京';

  • 锁机制:数据库为了保证数据的一致性,在共享的资源被并发访问时变得安全所设计的一种规则
  • 锁机制类似多线程中的同步,作用就是可以保证数据的一致性和安全性
  • 按操作分类
    • 共享锁:也叫读锁。针对同一份数据,多个事务读取操作可以同时加锁而不相互影响,但是不能修改数据
    • 排他锁:也叫写锁。当前的操作没有完成前,会阻断其他操作的读取和写入
  • 按粒度分类
    • 表级锁:会锁定整张表。开销小,加锁快。锁定粒度大,发生锁冲突概率高,并发度低。不会出现死锁情况
    • 行级锁:会锁定当前行。开销大,加锁慢。锁定粒度小,发生锁冲突概率低,并发度高。会出现死锁情况
  • 按使用方式分类
    • 悲观锁:每次查询数据时都认为别人会修改,很悲观,所以查询时加锁
    • 乐观锁:每次查询时都认为别人不会修改,很乐观,但是更新时会判断一下在此期间别人有没有去更新这个数据
  • 不同存储引擎支持的锁
    存储引擎 表锁 行锁
    InnoDB 支持 支持
    MyISAM 支持 不支持
    MEMORY 支持 不支持

注意: 在下面所有的锁的操作中,只提到了修改操作,但是增删都是和修改一样的

InnoDB共享锁

  • 共享锁特点
    数据可以被多个事务查询,但是不能修改
    InnoDB引擎默认加的是行锁,如果不采用带索引的列加锁时加的就是表锁
  • 创建共享锁格式
    SELECT语句 LOCK IN SHARE MODE;
/*
	数据准备
*/

-- 创建db10数据库
CREATE DATABASE db10;

-- 使用db10数据库
USE db10;

-- 创建student表
CREATE TABLE student(
	id INT PRIMARY KEY AUTO_INCREMENT,
	NAME VARCHAR(10),
	age INT,
	score INT
);

-- 添加数据
INSERT INTO student VALUES (NULL, '张三', 23, 99), (NULL, '李四', 24, 95),
(NULL, '王五', 25, 98), (NULL, '赵六', 26, 97);
/*
	窗口1
*/

-- 开启事务
START TRANSACTION;

-- 查询id为1的数据,并加入共享锁
SELECT * FROM student WHERE id = 1 LOCK IN SHARE MODE;

-- 查询分数为99的数据,并加入共享锁
SELECT * FROM student WHERE score = 99 LOCK IN SHARE MODE;

-- 提交事务
COMMIT;
/*
	窗口2
*/

-- 开启事务
START TRANSACTION;

-- 查询id为1的数据(普通查询没有问题)
SELECT * FROM student WHERE id = 1;

-- 查询id为1的数据,也加入共享锁(共享锁和共享锁之间相互兼容)
SELECT * FROM student WHERE id = 1 LOCK IN SHARE MODE;

-- 修改id为1的数据,姓名改为张三三(修改失败,出现锁的情况,只有在窗口一提交事务之后才能修改成功)
UPDATE student SET NAME = '张三三' WHERE id = 1;

-- 修改id为2的数据,将姓名修改为李四四(修改成功,InnoDB引擎默认加的是行锁)
UPDATE student SET NAME = '李四四' WHERE id = 2;

-- 修改id为3的数据,姓名改为王五五(修改失败,锁,InnoDB如果不采用带索引的列加锁时加的就是表锁)
UPDATE student SET NAME = '王五五' WHERE id = 3;

-- 提交事务(窗口2没提交事务时修改的内容在窗口1中不能查询到)
COMMIT;

InnoDB排他锁

  • 排他锁特点
    加锁的数据,不能被其他事务加锁查询或修改(普通查询可以)
    锁和锁之间不能共存
  • 创建排他锁的格式
    SELECT语句 FOR UPDATE;
/*
	窗口1
*/

-- 开启事务
START TRANSACTION;

-- 查询id为1的数据,并加入排他锁
SELECT * FROM student WHERE id = 1 FOR UPDATE;
SELECT * FROM student WHERE score = 99 FOR UPDATE;
-- 提交事务
COMMIT;
/*
	窗口2
*/

-- 开启事务
START TRANSACTION;

-- 查询id为1的数据(成功,普通查询没问题)
SELECT * FROM student WHERE id =1;

-- 查询id为1的数据,并加入共享锁(失败,排他锁和共享锁不兼容)
SELECT * FROM student WHERE id = 1 LOCK IN SHARE MODE;

-- 查询id为1的数据,并加入排他锁(失败,排他锁和排他锁也不兼容)
SELECT * FROM student WHERE id =1 FOR UPDATE;

-- 修改id为1的数据,姓名改为张三(失败,会出现锁的情况,只有窗口1提交事务后才能修改)
UPDATE student SET NAME = '张三' WHERE id = 1;

-- 提交事务
COMMIT;

MyISAM 读锁

  • 读锁特点
    所有连接只能查询数据,不能修改
    MyISAM存储引擎只能添加表锁,且不支持事务
  • 读锁语法格式
    • 加锁:LOCK TABLE 表名 READ;
    • 解锁:UNLOCK TABLES;
-- 创建product表
CREATE TABLE product(
	id INT PRIMARY KEY AUTO_INCREMENT,
	NAME VARCHAR(20),
	price INT
)ENGINE = MYISAM;

-- 添加数据
INSERT INTO product VALUES (NULL, '华为手机', 4999), (NULL, '小米手机', 2999),
(NULL, '苹果', 8999), (NULL, '中兴', 1999);
/*
	窗口1
*/

-- 为product表添加读锁
LOCK TABLE product READ;

-- 查询id为1的数据
SELECT * FROM product WHERE id = 1;

-- 修改id为1的数据,金额改为4999(失败,读锁中所有连接只能读取数据不能修改数据)
UPDATE product SET price = 4999 WHERE id = 1;

-- 解锁
UNLOCK TABLES;
/*
	窗口2
*/

-- 查询id为1的数据
SELECT * FROM product WHERE id = 1;

-- 修改id为1的数据,金额改为5999(失败,读锁中所有连接只能读取数据不能修改数据)
UPDATE product SET price = 5999 WHERE id = 1;

MyISAM 写锁

  • 写锁特点
    其他连接不能查询和修改数据(当前连接下可以查询和修改)
  • 写锁语法格式
    • 加锁:LOCK TABLE 表名 WRITE;
    • 解锁:UNLOCK TABLES;
/*
	窗口1
*/

-- 为product表添加写锁
LOCK TABLE product WRITE;

-- 查询(没有问题)
SELECT * FROM product;

-- 修改(没有问题)
UPDATE product SET price = 1999 WHERE id = 2;

-- 解锁
UNLOCK TABLES;
/*
	窗口2
*/

-- 查询(失败,出现锁,只有窗口1解锁后才能成功)
SELECT * FROM product;

-- 修改(失败,出现锁,只有窗口1解锁后才能成功)
UPDATE product SET price = 2999 WHERE id = 2;

悲观锁和乐观锁

  • 悲观锁
    就是很悲观,它对于数据被外界修改的操作持保守态度,认为数据随时会修改
    整个数据处理中需要将数据加锁。悲观锁一般都是依靠关系型数据库提供的锁机制
    我们之前所学习的锁机制都是悲观锁
  • 乐观锁
    就是很乐观,每次自己操作数据的时候认为没有人会来修改它,所以不去加锁
    但是在更新的时候会去判断在此期间数据有没有被修改
    需要用户自己去实现,不会发生并发抢占资源,只有在提交操作的时候检查是否违反数据完整性

乐观锁实现方式(了解)

  • 方式一
    • 给数据表中添加一个version列,每次更新后都将这个列的值加1
    • 读取数据时,将版本号读取出来,再执行更新的时候,比较版本号
    • 如果相同则执行更新,如果不同,说明此条数据已经发生了变化
    • 用户自行根据这个通知来决定怎么处理,比如重新开始一遍,或者放弃本次更新
  • 方式二
    • 和版本号方式基本一样,给数据表加入一个列,名称无所谓,数据类型是 timestamp
    • 每次更新后都将最新时间插入到此列
    • 读取数据时,将时间读取出来,在执行更新的时候,比较时间
    • 如果相同则更新,如果不相同,说明此条数据已经发生了变化
-- 创建city表
CREATE TABLE city(
	id INT PRIMARY KEY AUTO_INCREMENT,
	NAME VARCHAR(20),
	VERSION INT
);

-- 添加数据
INSERT INTO city VALUES (NULL, '北京', 1), (NULL, '上海', 1), (NULL, '广州', 1), (NULL, '深圳', 1);

-- 将北京修改为北京市
-- 1. 将北京的版本号读取出来
SELECT VERSION FROM city WHERE NAME = '北京';		-- 1
-- 2. 修改北京为北京市,版本号+1,并对比版本号是否相同
UPDATE city SET NAME = '北京市', VERSION = VERSION + 1 WHERE NAME = '北京' AND VERSION = 1;

MyCat

本部分内容理解就行,在最终项目时可能才会用到,后期还需要单独的学习

随着互联网的发展,数据的量级也是不断地增长,从GB 到 TB 到 PB。对数据的各种操作也是越来越困难,一台数据库服务器已经无法满足海量数据的存储需求,所以多台数据库服务器构成的数据库集群成了必然的方式。不过,还要保证数据的一致性,查询效率等,同时又要解决多台服务器间的通信、负载均衡等问题

  • MyCat可以针对多台服务器做统一的管理,连接时只需要连接MyCat
  • Mycat是一款出色的数据库集群软件,不仅支持MySQL,常用关系型数据库也都支持
  • 其实就是一个数据库中间件产品,支持MySQL集群。提供高可用性数据分片集群
  • 我们可以像使用MySQL一样使用MyCat。对于开发人员来说几乎感觉不到MyCat 的存在

安装

  1. MyCat官网
    http://www.mycat.io
  2. 通过CRT工具上传到linux
    put D:\Mycat-server-1.6.7.1-release-20190627191042-linux.tar.gz
  3. 解压并查看
    tar -zxvf mycat.tar.gz
    cd mycat
    ls
  4. 为mycat目录授权
    chmod -R 777 mycat
  5. 配置环境变量
    编辑文件:vi /etc/profile
    添加内容:export MYCAT_HOME=/root/mycat(最下方)
    加载文件:source /etc/profile
  6. 启动mycat
    进入目录:cd /root/mycat/bin
    执行启动:./mycat start
  7. 查看端口监听
    netstat -ant|grep 8066
  8. SQLyog连接mycat
    默认用户名:root
    默认密码:123456
    默认端口号:8066

集群环境

  • 集群模型
    • MyCat MySQL 主服务器
    • MySQL 从服务器
  • 克隆虚拟机
  • 修改第二个虚拟机的网卡,重新生成MAC地址
  • 修改第二个服务器MySQL 配置文件 uuid
    • 编辑配置文件:vi /var/lib/mysql/auto.cnf
    • 将 server-uuid 更改一个数字即可
  • 启动相关服务
    • 关闭两台服务器的防火墙:systemctl stop firewalld
    • 启动两台服务器的MySQL:service mysqld restart
    • 启动两台服务器的MyCat:cd /root/mycat/bin ./mycat restart
    • 查看两台服务器的监听端口:netstat -ant|grep 3306 netstat -ant|grep 8066

主从复制

  • 为了使用MyCat进行读写分离,我们先要配置MySQL 数据库的主从复制
  • 从服务器自动同步主服务器的数据,从而达到数据一致
  • 进而,我们可以在写操作时,只操作主服务器,而读操作,就可以操作从服务器了

配置

主服务器

  1. 在第一个服务器上,编辑mysql配置文件

    • 编辑mysql配置文件:vi /etc/my.cnf
    • 在[mysqld]下面加上
    // log-bin代表开启主从复制,server-id代表主从服务器的唯一标识
    log-bin=mysql-bin
    server-id=1
    innodb_flush_log_at_trx_commit=1
    sync_binlog=1
    
  2. 查看主服务器的配置

    • 重启mysql:service mysqld restart
    • 登录mysql:mysql -u root -p
    • 查看主服务的配置:show master status;
      需要记住 File 列和 Position 列的数据,将来配置从服务器需要使用

从服务器

  1. 在第二个服务器上,编辑mysql配置文件

    • 编辑mysql配置文件:vi /etc/my.cnf
    • 在[mysqld]下面加上:server-id=2
  2. 登录mysql:mysql -u root -p

  3. 执行

    use mysql;
    drop table slave_master_info;
    drop table slave_relay_log_info;
    drop table slave_worker_info;
    drop table innodb_index_stats;
    drop table innodb_table_stats;
    source /usr/share/mysql/mysql_system_tables.sql;
    
  4. 重启mysql,重新登录,配置从节点

    • 重启mysql:service mysqld restart
    • 重新登录mysql:mysql -u root -p
    • 执行:change master to master_host='192.168.59.143',master_port=3306,master_user='root',master_password='itheima',master_log_file='mysql-bin.000001',master_log_pos=154;
    • 开启从节点:start slave;
    • 查询结果:show slave status\G;
      Slave_IO_Running和Slave_SQL_Running都为YES才表示同步成功。
  5. 测试:在主服务器上创建一个db1数据库,查看从服务器上是否自动同步

读写分离

  • 写操作只写入主服务器,由于有主从复制,从服务器中也会自动同步数据
  • 读操作是读取从服务器中的数据

配置

  • 修改主服务器 server.xml:vi /root/mycat/conf/server.xml
    <user name="root">
        <property name="password">MyCat密码</property>
        <property name="schemas">MyCat逻辑数据库显示的名字(虚拟数据库名)</property>
    </user>
    
  • 修改主服务器 schema.xml:vi /root/mycat/conf/schema.xml
    <?xml version="1.0"?>
    <!DOCTYPE mycat:schema SYSTEM "schema.dtd">
    <mycat:schema xmlns:mycat="http://io.mycat/">
    
    	<schema name="和MyCat逻辑数据库名一致" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1"></schema>
    	
    	<dataNode name="dn1" dataHost="localhost1" database="db1" />
    	
    	<dataHost name="localhost1" maxCon="1000" minCon="10" balance="1"
    			  writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">
    		<heartbeat>select user()</heartbeat>
    		<!-- 主服务器负责写的操作 -->
    		<writeHost host="hostM1" url="localhost:3306" user="root" password="itheima">
    			<!-- 从服务器负责读的操作 -->
    			<readHost host="hostS1" url="192.168.59.182:3306" user="root" password="itheima" />
    		</writeHost>
    	</dataHost>
    </mycat:schema>
    
  • 重启MyCat
    进入mycat路径:cd /root/mycat/bin
    重启mycat:./mycat restart
    查看端口监听:netstat -ant|grep 8066

分库分表

  • 分库分表:将庞大的数据量拆分为不同的数据库和数据表进行存储
  • 水平拆分
    根据表的数据逻辑关系,将同一表中的数据按照某种条件,拆分到多台数据库服务器上,也叫做横向拆分
    例如:一张 1000万的大表,按照一模一样的结构,拆分成4个250万的小表,分别保存到4个数据库中
  • 垂直拆分
    根据业务的维度,将不同的表切分到不同的数据库上,也叫做纵向拆分
    例如:所有的动物表都保存到动物库中,所有的水果表都保存到水果库中,同类型的表保存在同一个库中

水平拆分

  • 修改主服务器中 server.xml:vi /root/mycat/conf/server.xml
    <!--配置主键方式 0代表本地文件方式-->
    <property name="sequnceHandlerType">0</property>
    
  • 修改主服务器中 sequence_conf.properties:vi /root/mycat/conf/sequence_conf.properties
    #default global sequence
    GLOBAL.HISIDS=			# 可以自定义关键字
    GLOBAL.MINID=10001		# 最小值
    GLOBAL.MAXID=20000		# 最大值
    
  • 修改主服务器中 schema.xml:vi /root/mycat/conf/schema.xml
    <?xml version="1.0"?>
    <!DOCTYPE mycat:schema SYSTEM "schema.dtd">
    <mycat:schema xmlns:mycat="http://io.mycat/">
    
    	<schema name="HEIMADB" checkSQLschema="false" sqlMaxLimit="100">
    		<table name="product" primaryKey="id" dataNode="dn1,dn2,dn3" rule="mod-long"/>
    	</schema>
    	
    	<dataNode name="dn1" dataHost="localhost1" database="db1" />
    	<dataNode name="dn2" dataHost="localhost1" database="db2" />
    	<dataNode name="dn3" dataHost="localhost1" database="db3" />
    	
    	<dataHost name="localhost1" maxCon="1000" minCon="10" balance="1"
    			  writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">
    		<heartbeat>select user()</heartbeat>
    		<!-- 主服务器负责写的操作 -->
    		<writeHost host="hostM1" url="localhost:3306" user="root" password="itheima">
    			<!-- 从服务器负责读的操作 -->
    			<readHost host="hostS2" url="192.168.59.182:3306" user="root" password="itheima" />
    		</writeHost>
    	</dataHost>
    </mycat:schema>
    
  • 修改主服务器中 rule:vi /root/mycat/conf/rule.xml
    <function name="mod-long" class="io.mycat.route.function.PartitionByMod">
    	<!-- 指定节点数量 -->
    	<property name="count">3</property>
    </function>
    

垂直拆分

  • 修改主服务器中 schema.xml:vi /root/mycat/conf/schema.xml
    <?xml version="1.0"?>
    <!DOCTYPE mycat:schema SYSTEM "schema.dtd">
    <mycat:schema xmlns:mycat="http://io.mycat/">
    
    	<schema name="HEIMADB" checkSQLschema="false" sqlMaxLimit="100">
    		<table name="product" primaryKey="id" dataNode="dn1,dn2,dn3" rule="mod-long"/>
    		
    		<!-- 动物类数据表 -->
    		<table name="dog" primaryKey="id" autoIncrement="true" dataNode="dn4" />
    		<table name="cat" primaryKey="id" autoIncrement="true" dataNode="dn4" />
        
           <!-- 水果类数据表 -->
    		<table name="apple" primaryKey="id" autoIncrement="true" dataNode="dn5" />
    		<table name="banana" primaryKey="id" autoIncrement="true" dataNode="dn5" />
    	</schema>
    	
    	<dataNode name="dn1" dataHost="localhost1" database="db1" />
    	<dataNode name="dn2" dataHost="localhost1" database="db2" />
    	<dataNode name="dn3" dataHost="localhost1" database="db3" />
    	
    	<dataNode name="dn4" dataHost="localhost1" database="db4" />
    	<dataNode name="dn5" dataHost="localhost1" database="db5" />
    	
    	<dataHost name="localhost1" maxCon="1000" minCon="10" balance="1"
    			  writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">
    		<heartbeat>select user()</heartbeat>
    		<!-- 主服务器负责写的操作 -->
    		<writeHost host="hostM1" url="localhost:3306" user="root"
    				   password="itheima">
    			<!-- 从服务器负责读的操作 -->
    			<readHost host="hostS1" url="192.168.59.182:3306" user="root" password="itheima" />
    		</writeHost>
    	</dataHost>
    </mycat:schema>
    

推荐阅读:【Java WEB】JDBC详解

猜你喜欢

转载自blog.csdn.net/qq_24980365/article/details/123381625