文章目录
一、MySQL概述
1、数据库概述
1.1 数据库作用
- 持久化(persistence):把数据保存到可掉电式存储设备中以供之后使用
- 持久化的主要作用是将内存中的数据存储在关系型数据库中,当然也可以存储在磁盘文件、XML数据文件中
1.2 数据库的相关概念
- DB:数据库(Database)即存储数据的“仓库”,其本质是一个文件系统。它保存了一系列有组织的数据
- DBMS:数据库管理系统(Database Management System)是一种操纵和管理数据库的大型软件,用于建立、使用和维护数据库,对数据库进行统一管理和 控制。用户通过数据库管理系统访问数据库中表内的数据。
- SQL:结构化查询语言(Structured Query Language),专门用来与数据库通信的语言
目前互联网上常见的数据库管理软件有 Oracle、MySQL、MS SQL Server、DB2、PostgreSQL、Access、SyBase、Informix这几种(查看数据库最新排名:https://db-engines.com/en/ranking)
2、MySQL概述
2.1 概述
MySQL是一个开放源码的关系型数据库管理系统,由瑞典 MySQL AB(创始人Michael Widenius)公司1995年开发,迅速成为开源数据库的No.1。2008年被Sun收购(10亿美金),2009年Sun被Oracle收购。MariaDB应运而生。(MySQL的创造者担心MySQL有闭源的风险,因此创建了MySQL的分支项目MariaDB)。MySQL6.x版本之后分为社区版和商业版。MySQL是一种关联数据库管理系统,将数据保存在不同的表中,而不是将所有数据放在一个大仓库内,这样就增加了速度并提高了灵活性。
MySQL是开源的,所以你不需要支付额外的费用。MySQL是可以定制的,采用了GPL(GUN General Public License)协议,你可以需改源码来开发自己的MySQL系统。MySQL支持大型的数据库。可以处理拥有上千万条记录的大型数据库。MySQL支持大型数据库,支持5000万条记录的数据仓库 ,32位系统表文件最大可支持4GB,64位系统支持最大的表文件为8TB。MySQL使用标准的SQL数据语言形式。MySQL可以允许运行于多个系统上,并且支持多种语言。这些编程语言包括C、C++、Python、Java、Perl、PHP、和Ruby等
2.2 RDBMS与非RDBMS
关系型数据库的优点:复杂查询可以用SQL语句方便的在一个表以及多个表之间做非常复杂的数据查询;事务支持使得对于安全性能很高的数据访问要求得以实现。
非关系型数据库,可看成传统关系型数据库的功能阉割版本,基于键值对存储数据,不需要经过SQL层的解析,性能非常高。同时通过减少不常用的功能,进一步提高性能
3、MySQL环境安装
3.1 MySQL的下载、安装、配置(win)
下载之前先进行卸载(关闭mysql服务,卸载MySQL,清理文件,清理注册表(可选),删除环境变量配置),MySQL有四个版本
- MySQL Community Server 社区版本 ,开源免费,自由下载,但不提供官方技术支持,适用于大多数普通用户
- MySQL Enterprise Edition 企业版本 ,需付费,不能在线下载,可以试用 30 天。提供了更多的功能和更完备的技术支持,更适合于对数据库的功能和可靠性要求较高的企业客户
- MySQL Cluster 集群版 ,开源免费。用于架设集群服务器,可将几个MySQL Server封装成一个Server。需要在社区版或企业版的基础上使用
- MySQL Cluster CGE 高级集群版 ,需付费
此外,官方还提供了MySQL Workbench(GUITOOL)一款专为MySQL设计的图形界面管理工具。MySQLWorkbench又分为两个版本,分别是社区版(MySQL Workbench OSS)、商用版(MySQLWorkbenchSE)。Windows平台下提供两种安装文件:MySQL二进制分发版(.msi安装文件)和免安装版(.zip压缩文件)。一般来讲,应当使用二进制分发版,因为该版本提供了图形化的安装向导过程,比其他的分发版使用起来要简单,不再需要其他工具启动就可以运行MySQL。最后需要将mysql的bin目录放在环境变量中
3.2 MySQL登录
# 启动 MySQL 服务命令
net start MySQL服务名
# 停止 MySQL 服务命令
net stop MySQL服务名
# 也可以进入服务自己去打开
# 登录
mysql -h 主机名 -P 端口号 -u 用户名 -p密码
mysql -h localhost -P 3306 -u root -pabc123
mysql -V
# 进入mysql后
select version();
exit/quit
3.3 MySQL演示使用
# 查看所有数据库
show databases;
- information_schema是 MySQL 系统自带的数据库,主要保存 MySQL 数据库服务器的系统信息,比如数据库的名称、数据表的名称、字段名称、存取权限、数据文件 所在的文件夹和系统使用的文件夹,等等。
- performance_schema是 MySQL 系统自带的数据库,可以用来监控 MySQL 的各类性能指标。
- sys数据库是 MySQL 系统自带的数据库,主要作用是以一种更容易被理解的方式展示 MySQL 数据库服务器的各类性能指标,帮助系统管理员和开发人员监控 MySQL 的技术性能。
- mysql数据库保存了 MySQL 数据库服务器运行时需要的系统信息,比如数据文件夹、当前使用的字符集、约束检查信息,等等。
# 数据库的默认编码改为utf8mb4,见下面的ini修改,记得重启服务
# 创建自己的数据库
create database 数据库名;
# 使用自己的数据库
use 数据库名;
# 查看某个库的所有表格
show tables; #要求前面有use语句
show tables from 数据库名;
# 创建新的表格
create table 表名称(
字段名 数据类型,
字段名 数据类型
);
#创建学生表
create table student(
id int,
name varchar(20) #说名字最长不超过 20 个字符
);
# 查看一个表的数据
select * from 数据库表名称;
#查看学生表的数据
select * from student;
# 添加一条记录
insert into 表名称 values(值列表);
#添加两条记录到student表中
insert into student values( 1 ,'张三');
insert into student values( 2 ,'李四');
# 查看表的创建信息
show create table 表名称\G
#查看student表的详细创建信息
show create table student\G
# 查看数据库的创建信息
show create database 数据库名\G
#查看atguigudb数据库的详细创建信息
show create database atguigudb\G
# 删除表格
drop table 表名称;
#删除学生表
drop table student;
# 删除数据库
drop database 数据库名;
#删除atguigudb数据库
drop database atguigudb;
MySQL图形化管理工具极大地方便了数据库的操作与管理,常用的图形化管理工具有:MySQL Workbench、phpMyAdmin、Navicat Preminum、MySQLDumper、SQLyog、dbeaver、MySQL ODBCConnector(推荐navicate)
# 如果图形化连接出现错误,原因是MySQL8之前的版本中加密规则是mysql_native_password,而在MySQL8之后,加密规则是caching_sha2_password
# 解决问题方法有两种,第一种是升级图形界面工具版本,第二种是把MySQL8用户登录密码加密规则还原成mysql_native_password
#使用mysql数据库
USE mysql;
#修改'root'@'localhost'用户的密码规则和密码
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'abc123';
#刷新权限
FLUSH PRIVILEGES;
如果root密码忘记
# 通过任务管理器或者服务管理,关掉mysqld(服务进程)
# 通过命令行+特殊参数开启
mysqld mysqld – defaults-file="D:\ProgramFiles\mysql\MySQLServer5.7Data\my.ini" –skip-grant-tables
# 此时,mysqld服务进程已经打开。并且不需要权限检查
# 无密码登陆服务器
mysql -uroot
# 修改权限表
use mysql;
update user set authentication_string=password('新密码') where user='root' and Host='localhost;
flush privileges;
# 最后重启mysql服务
3.4 MySQL目录结构与源码
MySQL的目录结构 | 说明 |
---|---|
bin目录 | 所有MySQL的可执行文件。如:mysql.exe |
MySQLInstanceConfig.exe | 数据库的配置向导,在安装时出现的内容 |
data目录 | 系统数据库所在的目录 |
my.ini文件 | MySQL的主要配置文件 |
D:\software\mysql\data | 用户创建的数据库所在的目录 |
这是我的my.ini配置文件,
[mysqld]
# 设置3306端口
port=3306
# 设置mysql的安装目录
basedir=D:\\software\\mysql
# 设置mysql数据库的数据的存放目录
datadir=D:\\software\\mysql\\data
# 允许最大连接数
max_connections=200
# 允许连接失败的次数。这是为了防止有人从该主机试图攻击数据库系统
max_connect_errors=10
# 服务端使用的字符集默认为UTFMB4
character-set-server=utf8mb4
# 创建新表时将使用的默认存储引擎
default-storage-engine=INNODB
# 默认使用“mysql_native_password”插件认证
default_authentication_plugin=mysql_native_password
[mysql]
# 设置mysql客户端默认字符集
default-character-set=utf8mb4
[client]
# 设置mysql客户端连接服务端时默认使用的端口
port=3306
default-character-set=utf8mb4
MySQL 源代码获取,首先,你要进入 MySQL下载界面。 这里你不要选择用默认的“Microsoft Windows”,而是要通过下拉栏,找到“Source Code”,在下面的操作系统版本里面, 选择 Windows(Architecture Independent),然后点击下载。接下来,把下载下来的压缩文件解压,我们就得到了 MySQL 的源代码。MySQL 是用 C++ 开发而成的,我简单介绍一下源代码的组成。mysql-8.0.22 目录下的各个子目录,包含了 MySQL 各部分组件的源代码
- sql 子目录是 MySQL 核心代码;
- libmysql 子目录是客户端程序 API;
- mysql-test 子目录是测试工具;
- mysys 子目录是操作系统相关函数和辅助函数
二、SQL查询
1、SQL详情
1.1 SQL分类
- DDL(Data Definition Languages、数据定义语言) ,这些语句定义了不同的数据库、表、视图、索引等数据库对象,还可以用来创建、删除、修改数据库和数据表的结构。主要的语句关键字包括CREATE、DROP、ALTER等
- **DML(Data Manipulation Language、数据操作语言) **,用于添加、删除、更新和查询数据库记录,并检查数据完整性。主要的语句关键字包括INSERT、DELETE、UPDATE、SELECT等。SELECT是SQL语言的基础,最为重要。
- DCL(Data Control Language、数据控制语言) ,用于定义数据库、表、字段、用户的访问权限和安全级别。主要的语句关键字包括GRANT、REVOKE、COMMIT、ROLLBACK、SAVEPOINT等。
因为查询语句使用的非常的频繁,所以很多人把查询语句单拎出来一类:DQL(数据查询语言)。还有单独将COMMIT、ROLLBACK 取出来称为TCL (Transaction Control Language,事务控制语言)。
1.2 SQL语言的规则与规范
基本规则
- SQL 可以写在一行或者多行。为了提高可读性,各子句分行写,必要时使用缩进
- 每条命令以 ; 或 \g 或 \G 结束
- 关键字不能被缩写也不能分行
- 关于标点符号
- 必须保证所有的()、单引号、双引号是成对结束的
- 必须使用英文状态下的半角输入方式
- 字符串型和日期时间类型的数据可以使用单引号(’ ')表示
- 列的别名,尽量使用双引号(" "),而且不建议省略as
SQL大小写规范 (建议遵守)
- MySQL 在 Windows 环境下是大小写不敏感的
- MySQL 在 Linux 环境下是大小写敏感的
- 数据库名、表名、表的别名、变量名是严格区分大小写的
- 关键字、函数名、列名(或字段名)、列的别名(字段的别名) 是忽略大小写的。
- 推荐采用统一的书写规范:
- 数据库名、表名、表别名、字段名、字段别名等都小写
- SQL 关键字、函数名、绑定变量等都大写
注释
单行注释:#注释文字(MySQL特有的方式)
单行注释:-- 注释文字(--后面必须包含一个空格。)
多行注释:/* 注释文字 */
命名规则(暂时了解)
- 数据库、表名不得超过 30 个字符,变量名限制为 29 个
- 必须只能包含 A–Z, a–z, 0–9, _共 63 个字符
- 数据库名、表名、字段名等对象名中间不要包含空格
- 同一个MySQL软件中,数据库不能同名;同一个库中,表不能重名;同一个表中,字段不能重名
- 必须保证你的字段没有和保留字、数据库系统或常用方法冲突。如果坚持使用,请在SQL语句中使用`(着重号)引起来
- 保持字段名和类型的一致性,在命名字段并为其指定数据类型的时候一定要保证一致性。假如数据类型在一个表里是整数,那在另一个表里可就别变成字符型了
数据导入指令
# 数据导入指令,需要用命令行
source d:\xxx.sql
# 通过FOREIGN_KEY_CHECKS解决,用法如下
set FOREIGN_KEY_CHECKS=0; #在导入前设置为不检查外键约束
set FOREIGN_KEY_CHECKS=1; #在导入后恢复检查外键约束
2、 基本的SELECT语句
SELECT * FROM departments;
-- 重命名一个列,as也可以省略;别名使用双引号,以便在别名中包含空格或特 殊的字符并区分大小写
SELECT last_name AS name, commission_pct comm FROM employees;
-- 去除重复行,如果有多个列,就会去重全部列的
SELECT DISTINCT department_id FROM employees;
-- 空值参与运算
-- 空值:null ( 不等同于0, '', 'null' )实际问题的解决方案:引入IFNULL
-- 空值不等于空字符串。一个空字符串的长度是 0,而一个空值的长 度是空。而且,在 MySQL 里面,空值是占用空间的
SELECT employee_id, salary "月工资", salary * (1 + IFNULL(commission_pct, 0)) * 12 "年工资" FROM employees;
-- 着重号 ``,保证你的字段没有和保留字、数据库系统或常见方法冲突
SELECT * FROM `order`;
-- 查询常数
SELECT '小张科技' as "公司名", employee_id, last_name FROM employees;
-- 显示表中字段的详细信息
DESCRIBE employees;
DESC employees;
-- 过滤数据
SELECT employee_id, last_name, job_id, department_id FROM employeesWHERE department_id = 90;
- Field:表示字段名称。
- Type:表示字段类型,这里 barcode、goodsname 是文本型的,price 是整数类型的。
- Null:表示该列是否可以存储NULL值。
- Key:表示该列是否已编制索引。
- PRI表示该列是表主键的一部分;
- UNI表示该列是UNIQUE索引的一 部分;
- MUL表示在列中某个给定值允许出现多次。
- Default:表示该列是否有默认值,如果有,那么值是多少。
- Extra:表示可以获取的与给定列有关的附加信息,例如AUTO_INCREMENT等
3、运算符
-- DUAL 伪表
-- 算术运算符
-- 一个整数类型的值对整数进行加法和减法操作,结果还是一个整数;
-- 一个整数类型的值对浮点数进行加法和减法操作,结果是一个浮点数;
-- 在Java中, + 的左右两边如果有字符串,那么表示字符串的拼接。但是在MySQL中 + 只表示数值相加。如果遇到非数值类型,先尝试转成数值,如果转失败,就按0计算。(注:MySQL 中字符串拼接要使用字符串函数CONCAT()实现)
-- 在数学运算中,0不能用作除数,在MySQL中,一个数除以0为NULL
SELECT 100 + 0, 100 + 50 * 30, 100 - 35.5 FROM DUAL;
-- 比较运算符
-- 比较的结果为真则返回1,比较的结果 为假则返回0,其他情况则返回NULL
-- 如果等号两边的值、字符串或表达式中有一个为NULL,则比较结果为NULL
SELECT 1 = 1, 1 = '1', 1 = 0, 'a' = 'a', (5 + 3) = (2 + 6), '' = NULL , NULL = NULL;
SELECT 1 = 2, 0 = 'abc', 1 = 'abc' FROM DUAL;
-- 如果等号两边的值、字符串或表达式都为字符串,则MySQL会按照字符串进行比较,其比较的 是每个字符串中字符的ANSI编码是否相等
-- 如果等号两边的值都是整数,则MySQL会按照整数来比较两个值的大小。
-- 如果等号两边的值一个是整数,另一个是字符串,则MySQL会将字符串转化为数字进行比较。
-- 如果等号两边的值、字符串或表达式中有一个为NULL,则比较结果为NULL
-- <=> 使用安全等于运算符时,两边的操作数的值都为NULL时,返回的结果为1而不是NULL,其他返回结果与等于运算符相同
SELECT 1 <=> '1', 1 <=> 0, 'a' <=> 'a', (5 + 3) <=> (2 + 6), '' <=> NULL,NULL <=> NULL FROM dual
-- 不等于运算符
SELECT 1 <> 1, 1 != 2, 'a' != 'b', (3+4) <> (2+6), 'a' != NULL, NULL <> NULL;
SELECT NULL IS NULL, ISNULL(NULL), ISNULL('a'), 1 IS NULL;
SELECT NULL IS NOT NULL, 'a' IS NOT NULL, 1 IS NOT NULL;
-- 最小值运算符
-- 语法格式为:LEAST(值 1 ,值 2 ,…,值n)。其中,“值n”表示参数列表中有n个值。在有两个或多个参数的情况下,返回最小值
SELECT LEAST ( 1 , 0 , 2 ), LEAST('b','a','c'), LEAST( 1 ,NULL, 2 );
-- 最大值运算符
SELECT GREATEST( 1 , 0 , 2 ), GREATEST('b','a','c'), GREATEST( 1 ,NULL, 2 );
-- BETWEEN AND运算符
-- BETWEEN运算符使用的格式通常为SELECT D FROM TABLE WHERE C BETWEEN A AND B,此时,当C大于或等于A,并且C小于或等于B时,结果为 1 ,否则结果为 0
SELECT 1 BETWEEN 0 AND 1 , 10 BETWEEN 11 AND 12 , 'b' BETWEEN 'a' AND 'c';
SELECT last_name, salary FROM employees WHERE salary BETWEEN 2500 AND 3500 ;
-- IN运算符
-- IN运算符用于判断给定的值是否是IN列表中的一个值,如果是则返回 1 ,否则返回 0 。如果给定的值为NULL,或者IN列表中存在NULL,则结果为NULL
SELECT employee_id, last_name, salary, manager_id FROM employees WHERE manager_id IN (100 ,101 ,201);
-- NOT IN运算符
SELECT 'a' NOT IN ('a','b','c'), 1 NOT IN ( 2 , 3 );
-- LIKE运算符,如果给定的值或者匹配条件为NULL,则返回结果为NULL
-- "%":匹配 0 个或多个字符
-- "_":只能匹配一个字符
SELECT NULL LIKE 'abc', 'abc' LIKE NULL;
SELECT last_name FROM employees WHERE last_name LIKE '_o%';
-- 回避特殊符号的: 使用转义符 例如:将[%]转为[$%]、[]转为[$],然后再加上[ESCAPE'$']即可
-- 默认 \是转义
SELECT job_id FROM jobs WHERE job_id LIKE 'IT\_%';
SELECT job_id FROM jobs WHERE job_id LIKE 'IT$_%' escape '$';
-- REGEXP运算符
-- REGEXP运算符用来匹配字符串,语法格式为:expr REGEXP 匹配条件
-- '^'匹配以该字符后面的字符开头的字符串。
-- '$'匹配以该字符前面的字符结尾的字符串。
-- '.'匹配任何一个单字符。
-- "[...]"匹配在方括号内的任何字符。例如,"[abc]"匹配"a"或"b"或"c"。为了命名字符的范围,使用一个'-'。"[a-z]"匹配任何字母,而"[0-9]"匹配任何数字。
-- '*'匹配零个或多个在它前面的字符。例如,"x*"匹配任何数量的'x'字符,"[0-9]*"匹配任何数量的数字,而"*"匹配任何数量的任何字符。
SELECT 'atguigu' REGEXP 'gu.gu', 'atguigu' REGEXP '[ab]';
逻辑运算符主要用来判断表达式的真假,在MySQL中,逻辑运算符的返回结果为1、0或者NULL
-- 逻辑非(NOT或!)运算符表示当给定的值为 0 时返回 1 ;当给定的值为非 0 值时返回 0 ;当给定的值为NULL时,返回NULL
SELECT NOT 1 , NOT 0 , NOT( 1 + 1 ), NOT! 1 , NOT NULL;
-- 逻辑与(AND或&&)运算符是当给定的所有值均为非 0 值,并且都不为NULL时,返回1 ;当给定的一个值或者多个值为 0 时则返回 0 ;否则返回NULL
SELECT 1 AND - 1 , 0 AND 1 , 0 AND NULL, 1 AND NULL;
-- 逻辑或(OR或||)运算符是当给定的值都不为NULL,并且任何一个值为非 0 值时,则返回 1 ,否则返回 0 ;当一个值为NULL,并且另一个值为非 0 值时,返回 1 ,否则返回NULL;当两个值都为NULL时,返回NULL
SELECT 1 OR - 1 , 1 OR 0 , 1 OR NULL, 0 || NULL, NULL || NULL;
-- 逻辑异或(XOR)运算符是当给定的值中任意一个值为NULL时,则返回NULL;如果两个非NULL的值都是 0 或者都不等于 0 时,则返回 0 ;如果一个值为 0 ,另一个值不为 0 时,则返回 1
SELECT 1 XOR - 1 , 1 XOR 0 , 0 XOR 0 , 1 XOR NULL, 1 XOR 1 XOR 1 , 0 XOR 0 XOR 0 ;
-- AND优先级高于OR
位运算符是在二进制数上进行计算的运算符。位运算符会先将操作数变成二进制数,然后进行位运算, 最后将计算结果从二进制变回十进制数
-- 按位与(&)运算符将给定值对应的二进制数逐位进行逻辑与运算。当给定值对应的二进制位的数值都为 1 时,则该位返回 1 ,否则返回 0 。
SELECT 1 & 10 , 20 & 30 ;
-- 按位或(|)运算符将给定的值对应的二进制数逐位进行逻辑或运算。当给定值对应的二进制位的数值有一个或两个为 1 时,则该位返回 1 ,否则返回 0
SELECT 1 | 10 , 20 | 30 ;
-- 按位异或(^)运算符将给定的值对应的二进制数逐位进行逻辑异或运算。当给定值对应的二进制位的数值不同时,则该位返回 1 ,否则返回 0
SELECT 1 ^ 10 , 20 ^ 30 ;
-- 按位取反(~)运算符将给定的值的二进制数逐位进行取反操作,即将 1 变为 0 ,将 0 变为 1 。
SELECT 10 & ~1;
-- 按位右移(>>)运算符将给定的值的二进制数的所有位右移指定的位数。右移指定的位数后,右边低位的数值被移出并丢弃,左边高位空出的位置用 0 补齐
SELECT 1 >> 2 , 4 >> 2 ;
-- 按位左移(<<)运算符将给定的值的二进制数的所有位左移指定的位数。左移指定的位数后,左边高位的数值被移出并丢弃,右边低位空出的位置用 0 补齐
SELECT 1 << 2 , 4 << 2 ;
4、排序和分页
4.1 排序规则
- 使用 ORDER BY 子句排序
- ASC(ascend): 升序
- DESC(descend):降序
- ORDER BY 子句在SELECT语句的结尾,列的别名只能在order by使用,不能在where使用,where需要在from后面
-- 单列排序
SELECT last_name, job_id, department_id, hire_date FROM employees ORDER BY hire_date;
-- 多列排序
-- 可以使用不在SELECT列表中的列排序
-- 在对多列进行排序的时候,首先排序的第一列必须有相同的列值,才会对第二列进行排序。
-- 如果第一列数据中所有值都是唯一的,将不再对第二列进行排序
4.2 分页
--前10条记录:
SELECT * FROM 表名 LIMIT 0,10;
SELECT * FROM 表名 LIMIT 10;
--第11至20条记录:
SELECT * FROM 表名 LIMIT 10,10;
--第21至30条记录:
SELECT * FROM 表名 LIMIT 20,10;
-- MySQL 8.0中可以使用“LIMIT 3 OFFSET 4”,意思是获取从第5条记录开始后面的3条记录,和“LIMIT 4,3;”返回的结果相同。
-- SELECT * FROM table LIMIT(PageNo - 1) * PageSize, PageSize;
LIMIT 子句必须放在整个SELECT语句的最后,约束返回结果的数量可以 减少数据表的网络传输量 ,也可以提升查询效率
5、多表查询(重要)
-- 自连接
SELECT CONCAT(worker.last_name , ' works for ', manager.last_name)
FROM employees worker, employees manager
WHERE worker.manager_id = manager.employee_id;
-- 内连接与外连接
-- 内连接: 合并具有同一列的两个以上的表的行, 结果集中不包含一个表与另一个表不匹配的行,会产生笛卡尔积
-- SQL92语法
SELECT emp.employee_id, dep.department_name
FROM employee emp, department dep
WHERE emp.`department_id` = dep.`department_id`;
-- SQL99语法
SELECT emp.employee_id, dep.department_name
FROM employee emp JOIN department dep
ON emp.`department_id` = dep.`department_id`;
-- 外连接: 两个表在连接过程中除了返回满足连接条件的行以外还返回左(或右)表中不满足条件的 行 ,这种连接称为左(或右) 外连接。没有匹配的行时, 结果表中相应的列为空(NULL)。
-- 如果是左外连接,则连接条件中左边的表也称为 主表 ,右边的表称为 从表
SELECT last_name, department_name
FROM employees emp LEFT OUTER JOIN department dep
ON emp.`department_id` = dep.`department_id`;
-- 如果是右外连接,则连接条件中右边的表也称为 主表 ,左边的表称为 从表
SELECT last_name, department_name
FROM employees emp RIGHT OUTER JOIN department dep
ON emp.`department_id` = dep.`department_id`;
-- UNION的使用
-- 利用UNION关键字,可以给出多条SELECT语句,并将它们的结果组合成单个结果集。
-- UNION 操作符返回两个查询的结果集的并集,去除重复记录
-- UNION ALL操作符返回两个查询的结果集的并集。对于两个结果集的重复部分,不去重
-- 执行UNION ALL语句时所需要的资源比UNION语句少
-- 查询部门编号>90或邮箱包含a的员工信息
SELECT * FROM employees WHERE email LIKE '%a%' OR department_id>90;
SELECT * FROM employees WHERE email LIKE '%a%'
UNION
SELECT * FROM employees WHERE department_id>90;
-- 查询中国用户中男性的信息以及美国用户中年男性的用户信息
SELECT id,cname FROM t_chinamale WHERE csex='男'
UNION ALL
SELECT id,tname FROM t_usmale WHERE tGender='male';
七种SQL JOINS的实现
SQL99语法的新特性(了解即可)
SQL99 在 SQL92 的基础上提供了一些特殊语法,比如 NATURAL JOIN 用来表示自然连接。我们可以把 自然连接理解为 SQL92 中的等值连接。它会帮你自动查询两张连接表中所有相同的字段 ,然后进行 等值 连接(MySQL复杂链接 (左/右/全外)链接只能使用SQL99语法,SQL92不支持)
-- SQL92
SELECT employee_id,last_name,department_name
FROM employees e JOIN departments d
ON e.`department_id` = d.`department_id`
AND e.`manager_id` = d.`manager_id`;
-- SQL99
SELECT employee_id,last_name,department_name
FROM employees e NATURAL JOIN departments d;
当我们进行连接的时候,SQL99还支持使用 USING 指定数据表里的 同名字段 进行等值连接。但是只能配 合JOIN一起使用。比如:
SELECT employee_id,last_name,department_name
FROM employees e JOIN departments d
USING (department_id);
-- 和下面的是一样的
SELECT employee_id,last_name,department_name
FROM employees e ,departments d
WHERE e.department_id = d.department_id;
6、内置函数
6.1 数值函数
基本函数
函数 | 用法 |
---|---|
ABS(x) | 返回x的绝对值 |
SIGN(X) | 单元格 |
PI() | 返回圆周率的值 |
CEIL(x),CEILING(x) | 返回大于或等于某个值的最小整数 |
FLOOR(x) | 返回小于或等于某个值的最大整数 |
LEAST(e1,e2,e3…) | 返回列表中的最小值 |
GREATEST(e1,e2,e3…) | 返回列表中的最大值 |
MOD(x,y) | 返回X除以Y后的余数 |
RAND() | 返回0~1的随机值 |
RAND(x) | 返回0~1的随机值,其中x的值用作种子值,相同的X值会产生相同的随机 数 |
ROUND(x) | 返回一个对x的值进行四舍五入后,最接近于X的整数 |
ROUND(x,y) | 返回一个对x的值进行四舍五入后最接近X的值,并保留到小数点后面Y位 |
TRUNCATE(x,y) | 返回数字x截断为y位小数的结果 |
SQRT(x) | 返回x的平方根。当X的值为负数时,返回NULL |
角度与弧度互换函数
函数 | 用法 |
---|---|
RADIANS(x) | 将角度转化为弧度,其中,参数x为角度值 |
DEGREES(x) | 将弧度转化为角度,其中,参数x为弧度值 |
三角函数
函数 | 用法 |
---|---|
SIN(x) | 将角度转化为弧度,其中,参数x为角度值 |
ASIN(x) | 将弧度转化为角度,其中,参数x为弧度值 |
COS(x) | 返回x的余弦值,其中,参数x为弧度值 |
ACOS(x) | 返回x的反余弦值,即获取余弦为x的值。如果x的值不在-1到1之间,则返回NULL |
TAN(x) | 返回x的正切值,其中,参数x为弧度值 |
ATAN(x) | 返回x的反正切值,即返回正切值为x的值 |
ATAN2(m,n) | 返回两个参数的反正切值 |
COT(x) | 返回x的余切值,其中,X为弧度值 |
指数与对数函数
函数 | 用法 |
---|---|
POW(x,y),POWER(X,Y) | 返回x的y次方 |
EXP(X) | 返回e的X次方,其中e是一个常数,2.718281828459045 |
LN(X),LOG(X) | 返回以e为底的X的对数,当X <= 0 时,返回的结果为NULL |
LOG10(X) | 返回以10为底的X的对数,当X <= 0 时,返回的结果为NULL |
LOG2(X) | 返回以2为底的X的对数,当X <= 0 时,返回NULL |
进制间的转换
函数 | 用法 |
---|---|
BIN(x) | 返回x的二进制编码 |
HEX(x) | 返回x的十六进制编码 |
OCT(x) | 返回x的八进制编码 |
CONV(x,f1,f2) | 返回f1进制数变成f2进制数 |
6.2 字符串函数
MySQL中,字符串的位置是从1开始的
函数 | 用法 |
---|---|
ASCII(S) | 返回字符串S中的第一个字符的ASCII码值 |
CHAR_LENGTH(s) | 返回字符串s的字符数。作用与CHARACTER_LENGTH(s)相同 |
LENGTH(s) | 返回字符串s的字节数,和字符集有关 |
CONCAT(s1,s2,…,sn) | 连接s1,s2,…,sn为一个字符串 |
CONCAT_WS(x, s1,s2,…,sn) | 同CONCAT(s1,s2,…)函数,但是每个字符串之间要加上x |
group_concat([distinct] s1 [order by s1desc] [Separator “;”]) | 将数据库中同一列的值压缩到一行,默认分割符号为, |
INSERT(str, idx, len, replacestr) | 将字符串str从第idx位置开始,len个字符长的子串替换为字符串replacestr |
REPLACE(str, a, b) | 用字符串b替换字符串str中所有出现的字符串a |
UPPER(s) 或 UCASE(s) | 将字符串s的所有字母转成大写字母 |
LOWER(s) 或LCASE(s) | 将字符串s的所有字母转成小写字母 |
LEFT(str,n) | 返回字符串str最左边的n个字符 |
RIGHT(str,n) | 返回字符串str最右边的n个字符 |
LPAD(str, len, pad) | 用字符串pad对str最左边进行填充,直到str的长度为len个字符 |
RPAD(str ,len, pad) | 用字符串pad对str最右边进行填充,直到str的长度为len个字符 |
LTRIM(s) | 去掉字符串s左侧的空格 |
RTRIM(s) | 去掉字符串s右侧的空格 |
TRIM(s) | 去掉字符串s开始与结尾的空格 |
TRIM(s1 FROM s) | 去掉字符串s开始与结尾的s1 |
TRIM(LEADING s1 FROM s) | 去掉字符串s开始处的s1 |
TRIM(TRAILING s1 FROM s) | 去掉字符串s结尾处的s1 |
REPEAT(str, n) | 返回str重复n次的结果 |
SPACE(n) | 返回n个空格 |
STRCMP(s1,s2) | 比较字符串s1,s2的ASCII码值的大小 |
SUBSTR(s,index,len) | 返回从字符串s的index位置其len个字符,作用与SUBSTRING(s,n,len)、 MID(s,n,len)相同 |
LOCATE(substr,str) | 返回字符串substr在字符串str中首次出现的位置,作用于POSITION(substr IN str)、INSTR(str,substr)相同。未找到,返回0 |
ELT(m,s1,s2,…,sn) | 返回指定位置的字符串,如果m=1,则返回s1,如果m=2,则返回s2,如果m=n,则返回sn |
FIELD(s,s1,s2,…,sn) | 返回字符串s在字符串列表中第一次出现的位置 |
FIND_IN_SET(s1,s2) | 返回字符串s1在字符串s2中出现的位置。其中,字符串s2是一个以逗号分隔的字符串 |
REVERSE(s) | 返回s反转后的字符串 |
NULLIF(value1,value2) | 比较两个字符串,如果value1与value2相等,则返回NULL,否则返回 value1 |
6.3 日期和时间函数
函数 | 用法 |
---|---|
CURDATE() ,CURRENT_DATE() | 返回当前日期,只包含年、月、日 |
CURTIME() , CURRENT_TIME() | 返回当前时间,只包含时、分、秒 |
NOW() / SYSDATE() / CURRENT_TIMESTAMP() / LOCALTIME() / LOCALTIMESTAMP() | 返回当前系统日期和时间 |
UTC_DATE() | 返回UTC(世界标准时间)日期 |
UTC_TIME() | 返回UTC(世界标准时间)时间 |
UNIX_TIMESTAMP() | 以UNIX时间戳的形式返回当前时间。SELECT UNIX_TIMESTAMP() - >1634348884 |
UNIX_TIMESTAMP(date) | 将时间date以UNIX时间戳的形式返回。 |
FROM_UNIXTIME(timestamp) | 将UNIX时间戳的时间转换为普通格式的时间 |
YEAR(date) / MONTH(date) / DAY(date) | 返回具体的日期值 |
HOUR(time)/MINUTE(time)/SECOND(time) | 返回具体的时间值 |
MONTHNAME(date) | 返回月份:January,… |
DAYNAME(date) | 返回星期几:MONDAY,TUESDAY……SUNDAY |
WEEKDAY(date) | 返回周几,注意,周1是0,周2是1,…周日是6 |
QUARTER(date) | 返回日期对应的季度,范围为 1 ~ 4 |
WEEK(date) , WEEKOFYEAR(date) | 返回一年中的第几周 |
DAYOFYEAR(date) | 返回日期是一年中的第几天 |
DAYOFMONTH(date) | 返回日期位于所在月份的第几天 |
DAYOFWEEK(date) | 返回周几,注意:周日是 1 ,周一是 2 ,…周六是7 |
EXTRACT(type FROM date) | 返回指定日期中特定的部分,type指定返回的值 |
TIME_TO_SEC(time) | 将time转化为秒并返回结果值。转化的公式为:小时 * 3600+分钟 * 60+秒 |
SEC_TO_TIME(seconds) | 将 seconds 描述转化为包含小时、分钟和秒的时间 |
DATE_ADD(datetime, INTERVAL expr type),ADDDATE(date,INTERVAL expr type) | 返回与给定日期时间相差INTERVAL时间段的日期时间 |
DATE_SUB(date,INTERVAL expr type),SUBDATE(date,INTERVAL expr type) | 返回与date相差INTERVAL时间间隔的日期 |
ADDTIME(time1,time2) | 返回time1加上time2的时间。当time2为一个数字时,代表的是秒,可以为负数 |
SUBTIME(time1,time2) | 返回time1减去time2后的时间。当time2为一个数字时,代表的是秒,可以为负数 |
DATEDIFF(date1,date2) | 返回date1 - date2的日期间隔天数 |
TIMEDIFF(time1, time2) | 返回time1 - time2的时间间隔 |
FROM_DAYS(N) | 返回从 0000 年 1 月 1 日起,N天以后的日期 |
TO_DAYS(date) | 返回日期date距离 0000 年 1 月 1 日的天数 |
LAST_DAY(date) | 返回date所在月份的最后一天的日期 |
MAKEDATE(year,n) | 针对给定年份与所在年份中的天数返回一个日期 |
MAKETIME(hour,minute,second) | 将给定的小时、分钟和秒组合成时间并返回 |
PERIOD_ADD(time,n) | 返回time加上n后的时间 |
DATE_FORMAT(date,fmt) | 按照字符串fmt格式化日期date值 |
TIME_FORMAT(time,fmt) | 按照字符串fmt格式化时间time值 |
GET_FORMAT(date_type,format_type) | 返回日期字符串的显示格式 |
STR_TO_DATE(str, fmt) | 按照字符串fmt对str进行解析,解析为一个日期 |
EXTRACT(type FROM date)函数中type的取值与含义:
上述非GET_FORMAT函数中fmt参数常用的格式符
格式符 | 说明 | 格式符 | 说明 |
---|---|---|---|
%Y | 4 位数字表示年份 | %y | 表示两位数字表示年份 |
%M | 月名表示月份(January,….) | %m | 两位数字表示月份(01,02,03…) |
%b | 缩写的月名(Jan.,Feb.,….) | %c | 数字表示月份(1,2,3,…) |
%D | 英文后缀表示月中的天数(1st,2nd,3rd,…) | %d | 两位数字表示月中的天数(01,02…) |
%e | 数字形式表示月中的天数(1,2,3,4,5……) | ||
%H | 两位数字表示小数, 24 小时制(01,02…) | %h和%I | 两位数字表示小时, 12 小时制(01,02…) |
%k | 数字形式的小时, 24 小时制(1,2,3) | %l | 数字形式表示小时, 12 小时制(1,2,3,4….) |
%i | 两位数字表示分钟(00,01,02) | %S和%s | 两位数字表示秒(00,01,02…) |
%W | 一周中的星期名称(Sunday…) | %a | 一周中的星期缩写(Sun.,Mon.,Tues.,…) |
%w | 以数字表示周中的天数(0=Sunday,1=Monday….) | ||
%j | 以 3 位数字表示年中的天数(001,002…) | %U | 以数字表示年中的第几周,(1,2,3…)其中Sunday为周中第一天 |
%u | 以数字表示年中的第几周,(1,2,3…)其中Monday为周中第一天 | ||
%T | 24 小时制 | %r | 12 小时制 |
%p | AM或PM | %% | 表示% |
GET_FORMAT函数中date_type和format_type参数取值如下
6.4 流程控制函数
流程处理函数可以根据不同的条件,执行不同的处理流程,可以在SQL语句中实现不同的条件选择。MySQL中的流程处理函数主要包括IF()、IFNULL()和CASE()函数
函数 | 用法 |
---|---|
IF(value,value1,value2) | 如果value的值为TRUE,返回value1,否则返回value |
IFNULL(value1, value2) | 如果value1不为NULL,返回value1,否则返回value |
CASE WHEN 条件1 THEN 结果1 WHEN 条件2 THEN 结果 2 …. [ELSE resultn] END | 相当于Java的if…else if…else… |
CASE expr WHEN 常量值1 THEN 值1 WHEN 常量值1 THEN 值1 …. [ELSE 值n] END | 相当于Java的switch…case… |
6.5 加密与解密函数
加密与解密函数主要用于对数据库中的数据进行加密和解密处理,以防止数据被他人窃取。这些函数在保证数据库安全时非常有用,下面ENCODE(value,password_seed)函数与DECODE(value,password_seed)函数互为反函数
函数 | 用法 |
---|---|
PASSWORD(str) | 返回字符串str的加密版本,41位长的字符串。加密结果不可逆 ,常用于用户的密码加密 |
MD5(str) | 返回字符串str的md5加密后的值,也是一种加密方式。若参数为 NULL,则会返回NULL |
SHA(str) | 从原明文密码str计算并返回加密后的密码字符串,当参数为 NULL时,返回NULL。 SHA加密算法比MD5更加安全 。 |
ENCODE(value,password_seed) | 返回使用password_seed作为加密密码加密value |
DECODE(value,password_seed) | 返回使用password_seed作为加密密码解密value |
6.6 MySQL信息函数
MySQL中内置了一些可以查询MySQL信息的函数,这些函数主要用于帮助数据库开发或运维人员更好地对数据库进行维护工作
函数 | 用法 |
---|---|
VERSION() | 返回当前MySQL的版本号 |
CONNECTION_ID() | 返回当前MySQL服务器的连接数 |
DATABASE(),SCHEMA() | 返回MySQL命令行当前所在的数据库 |
USER(),CURRENT_USER()、SYSTEM_USER(),SESSION_USER() | 返回当前连接MySQL的用户名,返回结果格式为“主机名@用户名” |
CHARSET(value) | 返回字符串value自变量的字符集 |
COLLATION(value) | 返回字符串value的比较规则 |
6.7 其他函数
MySQL中有些函数无法对其进行具体的分类,但是这些函数在MySQL的开发和运维过程中也是不容忽视的
函数 | 用法 |
---|---|
FORMAT(value,n) | 返回对数字value进行格式化后的结果数据。n表示四舍五入后保留到小数点后n位 |
CONV(value,from,to) | 将value的值进行不同进制之间的转换 |
INET_ATON(ipvalue) | 将以点分隔的IP地址转化为一个数字 |
INET_NTOA(value) | 将数字形式的IP地址转化为以点分隔的IP地址 |
BENCHMARK(n,expr) | 将表达式expr重复执行n次。用于测试MySQL处理expr表达式所耗费的时间 |
CONVERT(value USING char_code) | 将value所使用的字符编码修改为char_code |
7、聚合函数
7.1 聚合函数介绍
聚合(或聚集、分组)函数,它是对一组数据进行汇总的函数,输入的是一组数据的集合,输出的是单个值。聚合函数类型:AVG()、SUM()、MAX()、MIN()、COUNT()
-- AVG和SUM函数
SELECT AVG(salary), MAX(salary),MIN(salary), SUM(salary)
FROM employees
WHERE job_id LIKE '%REP%';
-- MIN和MAX函数
SELECT MIN(hire_date), MAX(hire_date)
FROM employees;
-- COUNT函数,适用于 任意数据类型
SELECT COUNT(*)
FROM employees
WHERE department_id = 50;
-- COUNT(expr) 返回 expr不为空 的记录总数
SELECT COUNT(commission_pct)
FROM employees
WHERE department_id = 50 ;
-- 用count(*),count(1),count(列名)谁好呢
-- 其实,对于MyISAM引擎的表是没有区别的。这种引擎内部有一计数器在维护着行数。
-- Innodb引擎的表用count(*),count(1)直接读行数,复杂度是O(n),因为innodb真的要去数一遍。但好于具体的count(列名)
-- 能不能使用count(列名)替换count(*)?
-- 不要使用 count(列名)来替代 count(*),count(*)是 SQL92 定义的标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关
-- count(*)会统计值为 NULL 的行,而 count(列名)不会统计此列为 NULL 值的行
7.2 GROUP BY
可以使用GROUP BY子句将表中的数据分成若干组,WHERE一定放在FROM后面
SELECT column, group_function(column)
FROM table
[WHERE condition]
[GROUP BY group_by_expression]
[ORDER BY column];
-- 在SELECT列表中所有未包含在组函数中的列都应该包含在 GROUP BY子句中
SELECT department_id, AVG(salary)
FROM employees
GROUP BY department_id ;
-- 包含在 GROUP BY 子句中的列不必包含在SELECT 列表中
SELECT AVG(salary)
FROM employees
GROUP BY department_id ;
-- 使用多个列分组
SELECT department_id dept_id, job_id, SUM(salary)
FROM employees
GROUP BY department_id, job_id ;
-- GROUP BY中使用WITH ROLLUP
-- 当使用ROLLUP时,不能同时使用ORDER BY子句进行结果排序,即ROLLUP和ORDER BY是互相排斥的
-- 使用WITH ROLLUP关键字之后,在所有查询出的分组记录之后增加一条记录,该记录计算查询出的所有记录的总和,即统计记录数量
SELECT department_id,AVG(salary)
FROM employees
WHERE department_id > 80
GROUP BY department_id WITH ROLLUP;
7.3 HAVING
过滤分组:HAVING子句、行已经被分组、使用了聚合函数、满足HAVING 子句中条件的分组将被显示、HAVING 不能单独使用,必须要跟 GROUP BY 一起使用
SELECT department_id, MAX(salary)
FROM employees
GROUP BY department_id
HAVING MAX(salary)> 10000 ;
-- 非法使用聚合函数 : 不能在 WHERE 子句中使用聚合函数
WHERE和HAVING的对比
- 区别 1 :WHERE 可以直接使用表中的字段作为筛选条件,但不能使用分组中的计算函数作为筛选条件;HAVING 必须要与 GROUP BY 配合使用,可以把分组计算的函数和分组字段作为筛选条件。这决定了,在需要对数据进行分组统计的时候,HAVING 可以完成 WHERE 不能完成的任务。这是因为,在查询语法结构中,WHERE 在 GROUP BY 之前,所以无法对分组结果进行筛选。HAVING 在 GROUP BY 之后,可以使用分组字段和分组中的计算函数,对分组的结果集进行筛选,这个功能是 WHERE 无法完成的。另外,WHERE排除的记录不再包括在分组中。
- 区别 2 :如果需要通过连接从关联表中获取需要的数据,WHERE 是先筛选后连接,而 HAVING 是先连接后筛选。 这一点,就决定了在关联查询中,WHERE 比 HAVING 更高效。因为 WHERE 可以先筛选,用一个筛选后的较小数据集和关联表进行连接,这样占用的资源比较少,执行效率也比较高。HAVING 则需要先把结果集准备好,也就是用未被筛选的数据集进行关联,然后对这个大的数据集进行筛选,这样占用的资源就比较多,执行效率也较低。
优点 | 缺点 | |
---|---|---|
WHERE | 先筛选数据再关联,执行效率高 | 不能使用分组中的计算函数进行筛选 |
HAVING | 可以使用分组中的计算函数 | 在最后的结果集中进行筛选,执行效率较低 |
7.4 SELECT的执行过程
#方式 1 :
SELECT ...,....,...
FROM ...,...,....
WHERE 多表的连接条件
AND 不包含组函数的过滤条件
GROUP BY ...,...
HAVING 包含组函数的过滤条件
ORDER BY ... ASC/DESC
LIMIT ...,...
#方式 2 :
SELECT ...,....,...
FROM ... JOIN ...
ON 多表的连接条件
JOIN ...
ON ...
WHERE 不包含组函数的过滤条件
AND/OR 不包含组函数的过滤条件
GROUP BY ...,...
HAVING 包含组函数的过滤条件
ORDER BY ... ASC/DESC
LIMIT ...,...
#其中:
#( 1 )from:从哪些表中筛选
#( 2 )on:关联多表查询时,去除笛卡尔积
#( 3 )where:从表中筛选的条件
#( 4 )group by:分组依据
#( 5 )having:在统计结果中再次筛选
#( 6 )order by:排序
#( 7 )limit:分页
-- sql语句执行
SELECT ... FROM ... JOIN ... ON ... WHERE ... GROUP BY ... HAVING ... ORDER BY ... LIMIT...
-- 语句执行顺序
FROM -> ON -> (JOIN) -> WHERE -> GROUP BY -> HAVING -> SELECT 的字段 -> DISTINCT -> ORDER BY -> LIMIT
-- 在 SELECT 语句执行这些步骤的时候,每个步骤都会产生一个虚拟表,然后将这个虚拟表传入下一个步骤中作为输入。需要注意的是,这些步骤隐含在 SQL 的执行过程中,对于我们来说是不可见的
SQL的执行原理
SELECT 是先执行 FROM 这一步的。在这个阶段,如果是多张表联查,还会经历下面的几个步骤:
- 首先先通过 CROSS JOIN 求笛卡尔积,相当于得到虚拟表 vt(virtual table)1-1;
- 通过 ON 进行筛选,在虚拟表 vt1-1 的基础上进行筛选,得到虚拟表 vt1-2;
- 添加外部行。如果我们使用的是左连接、右链接或者全连接,就会涉及到外部行,也就是在虚拟
表 vt1-2 的基础上增加外部行,得到虚拟表 vt1-3。
当然如果我们操作的是两张以上的表,还会重复上面的步骤,直到所有表都被处理完为止。这个过程得到是我们的原始数据。当我们拿到了查询数据表的原始数据,也就是最终的虚拟表 vt1,就可以在此基础上再进行 WHERE 阶段。在这个阶段中,会根据 vt1 表的结果进行筛选过滤,得到虚拟表 vt2。然后进入第三步和第四步,也就是 GROUP 和 HAVING 阶段。在这个阶段中,实际上是在虚拟表 vt2 的基础上进行分组和分组过滤,得到中间的虚拟表 vt3 和 vt4。
当我们完成了条件筛选部分之后,就可以筛选表中提取的字段,也就是进入到 SELECT 和 DISTINCT阶段。首先在 SELECT 阶段会提取想要的字段,然后在 DISTINCT 阶段过滤掉重复的行,分别得到中间的虚拟表vt5- 1 和 vt5- 2 。当我们提取了想要的字段数据之后,就可以按照指定的字段进行排序,也就是 ORDER BY 阶段,得到虚拟表 vt6。最后在 vt6 的基础上,取出指定行的记录,也就是 LIMIT 阶段,得到最终的结果,对应的是虚拟表vt7。
当然我们在写 SELECT 语句的时候,不一定存在所有的关键字,相应的阶段就会省略。同时因为 SQL 是一门类似英语的结构化查询语言,所以我们在写 SELECT 语句的时候,还要注意相应的关键字顺序, 所谓底层运行的原理,就是我们刚才讲到的执行顺序。
8、子查询(重要)
8.1 子查询基本使用
- 查询(内查询)在主查询之前一次执行完成
- 子查询的结果被主查询(外查询)使用
注意事项
- 子查询要包含在括号内
- 将子查询放在比较条件的右侧
- 单行操作符对应单行子查询,多行操作符对应多行子查询
-- 方式一:
SELECT salary
FROM employees
WHERE last_name = 'Abel';
SELECT last_name,salary
FROM employees
WHERE salary > 11000 ;
-- 方式二:自连接
SELECT e2.last_name,e2.salary
FROM employees e1,employees e
WHERE e1.last_name = 'Abel'
AND e1.`salary` < e2.`salary`
-- 方式三:子查询
SELECT last_name,salary
FROM employees
WHERE salary > (
SELECT salary
FROM employees
WHERE last_name = 'Abel'
);
子查询的分类
我们按内查询的结果返回一条还是多条记录,将子查询分为单行子查询、多行子查询
我们按内查询是否被执行多次,将子查询划分为相关(或关联)子查询和不相关(或非关联)子查询。子查询从数据表中查询了数据结果,如果这个数据结果只执行一次,然后这个数据结果作为主查询的条件进行执行,那么这样的子查询叫做不相关子查询。同样,如果子查询需要执行多次,即采用循环的方式,先从外部查询开始,每次都传入子查询进行查询,然后再将结果反馈给外部,这种嵌套的执行方式就称为相关子查询
8.1 单行子查询
-- 查询与 141 号或 174 号员工的manager_id和department_id相同的其他员工的employee_id,manager_id,department_id
SELECT employee_id, manager_id, department_id
FROM employees
WHERE (manager_id, department_id) IN
(SELECT manager_id, department_id
FROM employees
WHERE employee_id IN (141,174))
AND employee_id NOT IN (141,174);
-- 显式员工的employee_id,last_name和location
SELECT employee_id, last_name,
(CASE department_id
WHEN
(SELECT department_id FROM departments WHERE location_id = 1800)
THEN 'Canada' ELSE 'USA' END) location
FROM employees;
8.2 多行子查询
操作符 | 含义 |
---|---|
IN | 等于列表中的任意一个 |
ANY | 需要和单行比较操作符一起使用,和子查询返回的 某一个 值比较 |
ALL | 需要和单行比较操作符一起使用,和子查询返回的 所有 值比较 |
SOME | 实际上是ANY的别名,作用相同,一般常使用ANY |
-- mysql聚合函数不能嵌套使用
-- 查询平均工资最低的部门id
SELECT department_id
FROM employees
GROUP BY department_id
HAVING AVG(salary) <= ALL (
SELECT AVG(salary) avg_sal
FROM employees
GROUP BY department_id
)
-- 空值问题,包含了null
SELECT last_name
FROM employees
WHERE employee_id NOT IN (
SELECT manager_id
FROM employees
--where manager_id is not NULL
);
8.3 相关子查询
如果子查询的执行依赖于外部查询,通常情况下都是因为子查询中的表用到了外部的表,并进行了条件关联,因此每执行一次外部查询,子查询都要重新计算一次,这样的子查询就称之为关联子查询。相关子查询按照一行接一行的顺序执行,主查询的每一行都执行一次子查询
-- 查询员工的id,salary,按照department_name 排序
SELECT employee_id,salary
FROM employees e
ORDER BY (
SELECT department_name
FROM departments d
WHERE e.`department_id` = d.`department_id`
);
-- 在许多 DBMS 的处理过程中,对于自连接的处理速度要比子查询快得多
-- 子查询实际上是通过未知表进行查询后的条件判断,而自连接是通过已知的自身数据表进行条件判断,因此在大部分 DBMS 中都对自连接处理进行了优化
三、创建和管理表
1、基础知识
1.1 标识符命名规则
从系统架构的层次上看,MySQL 数据库系统从大到小依次是数据库服务器、数据库、数据表、数据表的行与列,命名标识:
- 数据库名、表名不得超过 30 个字符,变量名限制为 29 个
- 必须只能包含 A–Z, a–z, 0 – 9 , _共 63 个字符
- 数据库名、表名、字段名等对象名中间不要包含空格
- 同一个MySQL软件中,数据库不能同名;同一个库中,表不能重名;同一个表中,字段不能重名
- 必须保证你的字段没有和保留字、数据库系统或常用方法冲突。如果坚持使用,请在SQL语句中使用`(着重号)引起来
- 保持字段名和类型的一致性:在命名字段并为其指定数据类型的时候一定要保证一致性,假如数据类型在一个表里是整数,那在另一个表里可就别变成字符型了
1.2 MySQL中的数据类型
类型 | 类型举例 |
---|---|
整数类型 | TINYINT、SMALLINT、MEDIUMINT、 INT(或INTEGER) 、BIGINT |
浮点类型 | FLOAT、DOUBLE |
定点数类型 | DECIMAL |
位类型 | BIT |
日期时间类型 | YEAR、TIME、 DATE 、DATETIME、TIMESTAMP |
文本字符串类型 | CHAR、 VARCHAR 、TINYTEXT、TEXT、MEDIUMTEXT、LONGTEXT |
枚举类型 | ENUM |
集合类型 | SET |
二进制字符串类型 | BINARY、VARBINARY、TINYBLOB、BLOB、MEDIUMBLOB、LONGBLOB |
JSON类型 | JSON对象、JSON数组 |
空间数据类型 | 单值:GEOMETRY、POINT、LINESTRING、POLYGON; |
集合:MULTIPOINT、MULTILINESTRING、MULTIPOLYGON、GEOMETRYCOLLECTION |
常用的几类类型介绍如下:
数据类型 | 描述 |
---|---|
INT | 从-231到231-1的整型数据。存储大小为 4 个字节 |
CHAR(size) | 定长字符数据。若未指定,默认为 1 个字符,最大长度 255 |
VARCHAR(size) | 可变长字符数据,根据字符串实际长度保存, 必须指定长度 |
FLOAT(M,D) | 单精度,占用 4 个字节,M=整数位+小数位,D=小数位。 D<=M<=255,0<=D<=30,默认M+D<=6 |
DOUBLE(M,D) | 双精度,占用 8 个字节,D<=M<=255,0<=D<=30,默认M+D<=15 |
DECIMAL(M,D) | 高精度小数,占用M+2个字节,D<=M<=65,0<=D<=30,最大取值范围与DOUBLE相同。 |
DATE | 日期型数据,格式’YYYY-MM-DD’ |
BLOB | 二进制形式的长文本数据,最大可达4G |
TEXT | 长文本数据,最大可达4G |
2、创建和管理数据库
-- 创建数据库
CREATE DATABASE 数据库名;
-- 创建数据库并指定字符集,例如utf8mb4
CREATE DATABASE 数据库名 CHARACTER SET 字符集;
-- 判断数据库是否已经存在,不存在则创建数据库(推荐)
CREATE DATABASE IF NOT EXISTS 数据库名;
-- 如果MySQL中已经存在相关的数据库,则忽略创建语句,不再创建数据库
-- 注意,DATABASE 不能改名。一些可视化工具可以改名,它是建新库,把所有表复制到新库,再删旧库完成的
-- 查看当前所有的数据库
-- 有一个S,代表多个数据库
SHOW DATABASES;
-- 查看当前正在使用的数据库,使用的一个 mysql 中的全局函数
SELECT DATABASE();
-- 查看指定库下所有的表
SHOW TABLES FROM 数据库名;
-- 查看数据库的创建信息
SHOW CREATE DATABASE 数据库名;
-- 或者
SHOW CREATE DATABASE 数据库名\G
-- 使用/切换数据库
USE 数据库名;
-- 注意:要操作表格和数据之前必须先说明是对哪个数据库进行操作,否则就要对所有对象加上“数据库名.”
-- 更改数据库字符集
-- 比如:gbk、utf8等
ALTER DATABASE 数据库名 CHARACTER SET 字符集;
-- 删除数据库
-- 删除指定的数据库
DROP DATABASE 数据库名;
-- 删除指定的数据库(推荐)
DROP DATABASE IF EXISTS 数据库名;
3、创建表
创建表必须具备CREATE TABLE权限和存储空间
-- 第一种方式创建
-- 加上了IF NOT EXISTS关键字,则表示:如果当前数据库中不存在要创建的数据表,则创建数据表;
-- 如果当前数据库中已经存在要创建的数据表,则忽略建表语句,不再创建数据表。
-- 必须指定表名,列名(或字段名),数据类型, 长度;可选约束条件,默认值
CREATE TABLE [IF NOT EXISTS] 表名(
字段1, 数据类型 [约束条件] [默认值],
字段2, 数据类型 [约束条件] [默认值],
字段3, 数据类型 [约束条件] [默认值],
......
[表约束条件]
);
-- 举例
-- 创建表
CREATE TABLE emp (
-- int类型
emp_id INT,
-- 最多保存 20 个中英文字符
emp_name VARCHAR( 20 ),
-- 总位数不超过 15 位
salary DOUBLE,
-- 日期类型
birthday DATE
);
-- 查看一下表格
-- 在MySQL 8.x版本中,不再推荐为INT类型指定显示长度,并在未来的版本中可能去掉这样的语法
DESC emp;
-- 举例2
CREATE TABLE dept(
-- int类型,自增
deptno INT( 2 ) AUTO_INCREMENT,
dname VARCHAR( 14 ),
loc VARCHAR( 13 ),
-- 主键
PRIMARY KEY (deptno)
);
-- 第二种方式创建
-- 使用 AS subquery 选项, 将创建表和插入数据结合起来,指定的列和子查询中的列要一一对应,通过列名和默认值定义列
CREATE TABLE emp1 AS SELECT * FROM employees;
-- 创建的emp2是空表
CREATE TABLE emp2 AS SELECT * FROM employees WHERE 1 = 2 ;
-- 创建
CREATE TABLE dept
AS
SELECT employee_id, last_name, salary* 12 ANNSAL, hire_date
FROM employees
WHERE department_id = 80 ;
-- 查看一下
DESCRIBE dept80;
-- 查看数据表结构
-- 在MySQL中创建好数据表之后,可以查看数据表的结构。MySQL支持使用DESCRIBE/DESC语句查看数据表结构,
-- 也支持使用SHOW CREATE TABLE语句查看数据表结构
-- 使用SHOW CREATE TABLE语句不仅可以查看表创建时的详细语句,还可以查看存储引擎和字符编码
SHOW CREATE TABLE 表名\G
4、修改表
-- 修改表指的是修改数据库中已经存在的数据表的结构
-- 追加一个列
ALTER TABLE 表名 ADD 【COLUMN】 字段名 字段类型 【FIRST|AFTER 字段名】;
-- 举例
ALTER TABLE dept
ADD job_id varchar( 15 );
-- 修改一个列,可以修改列的数据类型,长度、默认值和位置
ALTER TABLE 表名 MODIFY 【COLUMN】 字段名 1 字段类型 【DEFAULT 默认值】【FIRST|AFTER 字段名2】;
-- 举例,对默认值的修改只影响今后对表的修改
ALTER TABLE dept
MODIFY salary double( 9 , 2 ) default 1000 ;
-- 重命名一个列,使用 CHANGE old_column new_column dataType子句重命名列
ALTER TABLE 表名 CHANGE 【column】 列名 新列名 新数据类型;
-- 举例
ALTER TABLE dept
CHANGE department_name dept_name varchar( 15 );
-- 删除一个列
ALTER TABLE 表名 DROP 【COLUMN】字段名
-- 举例
ALTER TABLE dept
DROP COLUMN job_id;
-- 重命名表,必须是对象的拥有者
RENAME TABLE emp TO myemp;
ALTER table dept RENAME [TO] detail_dept; -- [TO]可以省略
5、删除表
-- 删除表
-- 在MySQL中,当一张数据表没有与其他任何数据表形成关联关系时,可以将当前数据表直接删除。
-- 数据和结构都被删除,所有正在运行的相关事务被提交,所有相关索引被删除
-- IF EXISTS的含义为:如果当前数据库中存在相应的数据表,则删除数据表;如果当前数据库中不存在相应的数据表,则忽略删除语句,不再执行删除数据表的操作。
DROP TABLE [IF EXISTS] 数据表1 [, 数据表2, ..., 数据表n];
-- 举例,DROP TABLE 语句不能回滚
DROP TABLE dept80;
-- 清空表
-- TRUNCATE TABLE语句删除表中所有的数据,释放表的存储空间
-- TRUNCATE语句 不能回滚 ,而使用 DELETE 语句删除数据,可以回滚
TRUNCATE TABLE detail_dept;
-- TRUNCATE语句 不能回滚 ,而使用 DELETE 语句删除数据,可以回滚,举例
SET autocommit = FALSE;
DELETE FROM emp2;
-- TRUNCATE TABLE emp2;
SELECT * FROM emp2;
ROLLBACK;
SELECT * FROM emp2;
-- TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少,但 TRUNCATE 无事务且不触发 TRIGGER,有可能造成事故,故不建议在开发代码中使用此语句
-- TRUNCATE TABLE 在功能上与不带 WHERE 子句的 DELETE 语句相同。
6、内容扩展
6.1 MySQL字段命名
- 【强制】表名、字段名必须使用小写字母或数字,禁止出现数字开头,禁止两个下划线中间只出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。
- 正例:aliyun_admin,rdc_config,level3_name
- 反例:AliyunAdmin,rdcConfig,level_3_name
- 【强制】禁用保留字,如 desc、range、match、delayed 等,请参考 MySQL 官方保留字。
- 【强制】表必备三字段:id, gmt_create, gmt_modified。
- 说明:其中id必为主键,类型为BIGINT UNSIGNED、单表时自增、步长为1。gmt_create,gmt_modified 的类型均为 DATETIME 类型,前者现在时表示主动式创建,后者过去分词表示被动式更新
- 【推荐】表的命名最好是遵循 “业务名称_表的作用”。
- 正例:alipay_task 、 force_project、 trade_config
- 【推荐】库名与应用名称尽量一致。
- 【参考】合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检索速度。
- 正例:无符号值可以避免误存负数,且扩大了表示范围。
6.2 清空表、删除表等操作需谨慎
表删除操作将把表的定义和表中的数据一起删除,并且MySQL在执行删除操作时,不会有任何的确认信息提示,因此执行删除操时应当慎重。在删除表前,最好对表中的数据进行备份,这样当操作失误时可以对数据进行恢复,以免造成无法挽回的后果。
同样的,在使用 ALTER TABLE 进行表的基本修改操作时,在执行操作过程之前,也应该确保对数据进行完整的备份,因为数据库的改变是无法撤销的,如果添加了一个不需要的字段,可以将其删除;相同的,如果删除了一个需要的列,该列下面的所有数据都将会丢失。
6.3 MySQL 8 新特性—DDL的原子化
在MySQL 8.0版本中,InnoDB表的DDL支持事务完整性,即DDL操作要么成功要么回滚。DDL操作回滚日志写入到data dictionary数据字典表mysql.innodb_ddl_log(该表是隐藏的表,通过show tables无法看到)中,用于回滚操作。通过设置参数,可将DDL操作日志打印输出到MySQL错误日志中。
CREATE DATABASE mytest;
USE mytest;
CREATE TABLE book1(
book_id INT ,
book_name VARCHAR( 255 )
);
SHOW TABLES;
ROP TABLE book1,book2;
-- mysql5.7 book1被删除
-- mysql8.0 没有被删除
四、数据处理之增删改
1、插入数据
-- VALUES的方式添加
-- 为表的所有字段按默认顺序插入数据
INSERT INTO 表名 VALUES (value1,value2,....);
-- 举例
INSERT INTO departments VALUES ( 100 , 'Finance', NULL, NULL);
-- 为表的指定字段插入数据
INSERT INTO 表名(column1 [, column2, ..., columnn]) VALUES (value1 [,value2, ..., valuen]);
-- 为表的指定字段插入数据,就是在INSERT语句中只向部分字段中插入值,而其他字段的值为表定义时的默认值
INSERT INTO departments(department_id, department_name) VALUES ( 80 , 'IT');
-- 同时插入多条记录
-- INSERT语句可以同时向数据表中插入多条记录,插入时指定多个值列表,每个值列表之间用逗号分隔开
INSERT INTO table_name
VALUES
(value1 [,value2, ..., valuen]),
(value1 [,value2, ..., valuen]),
......
(value1 [,value2, ..., valuen]);
-- 使用INSERT同时插入多条记录时,MySQL会返回一些在执行单行插入时没有的额外信息
-- Records:表明插入的记录条数,Duplicates:表明插入时被忽略的记录,原因可能是这些记录包含了重复的主键值,Warnings:表明有问题的数据值,例如发生数据类型转换
-- 一个同时插入多行记录的INSERT语句等同于多个单行插入的INSERT语句,但是多行的INSERT语句在处理过程中效率更高
-- VALUES也可以写成VALUE,但是VALUES是标准写法
-- 字符和日期型数据应包含在单引号中
-- 将查询结果插入到表中
-- INSERT还可以将SELECT语句查询的结果插入到表中,此时不需要把每一条记录的值一个一个输入,只需要使用一条INSERT语句和一条SELECT语句组成的组合语句即可快速地从一个或多个表中向一个表中插入多行
INSERT INTO 目标表名
(tar_column1 [, tar_column2, ..., tar_columnn])
SELECT
(src_column1 [, src_column2, ..., src_columnn])
FROM 源表名
[WHERE condition]
-- 举例,子查询中的值列表应与 INSERT 子句中的列名对应
INSERT INTO emp
SELECT *
FROM employees
WHERE department_id = 90 ;
2、更新数据
UPDATE table_name
SET column1=value1, column2=value2, ... , column=valuen
[WHERE condition]
-- 可以一次更新 多条 数据
-- 如果需要回滚数据,需要保证在DML前,进行设置: SET AUTOCOMMIT = FALSE;
-- 使用 WHERE 子句指定需要更新的数据
-- 如果省略 WHERE 子句,则表中的所有数据都将被更新
UPDATE copy_emp
SET department_id = 110 ;
-- 注意约束问题,会报错
UPDATE employees SET department_id = 55 WHERE department_id = 110 ;
3、删除数据
-- table_name指定要执行删除操作的表;“[WHERE ]”为可选参数,指定删除条件,如果没有WHERE子句,DELETE语句将删除表中的所有记录
DELETE FROM table_name [WHERE <condition>];
-- 举例
DELETE FROM departments WHERE department_name = 'Finance';
-- 注意约束问题,会报错
DELETE FROM departments WHERE department_id = 60 ;
4、MySQL 8 新特性:计算列
什么叫计算列呢?简单来说就是某一列的值是通过别的列计算得来的。例如,a列值为 1 、b列值为 2 ,c列不需要手动插入,定义a+b的结果为c的值,那么c就是计算列,是通过别的列计算得来的
-- 举例:定义数据表tb1,然后定义字段id、字段a、字段b和字段c,其中字段c为计算列,用于计算a+b的值。 首先创建测试表tb1
CREATE TABLE tb1(
id INT,
a INT,
b INT,
c INT GENERATED ALWAYS AS (a + b) VIRTUAL
);
-- 插入演示数据
INSERT INTO tb1(a,b) VALUES ( 100 , 200 );
SELECT * FROM tb1;
UPDATE tb1 SET a = 500 ;
5、综合案例
#1、创建数据库test01_library
CREATE DATABASE IF NOT EXISTS test01_library CHARACTER SET 'utf8';
#指定使用哪个数据库
USE test01_library;
#2、创建表 books
CREATE TABLE books(
id INT,
name VARCHAR( 50 ),
`authors` VARCHAR( 100 ) ,
price FLOAT,
pubdate YEAR ,
note VARCHAR( 100 ),
num INT
);
#3、向books表中插入记录
1)不指定字段名称,插入第一条记录
INSERT INTO books
VALUES( 1 ,'Tal of AAA','Dickes', 23 , 1995 ,'novel', 11 );
2)指定所有字段名称,插入第二记录
INSERT INTO books (id,name,`authors`,price,pubdate,note,num)
VALUES( 2 ,'EmmaT','Jane lura', 35 , 1993 ,'Joke', 22 );
3)同时插入多条记录(剩下的所有记录)
INSERT INTO books (id,name,`authors`,price,pubdate,note,num) VALUES
(3 ,'Story of Jane','Jane Tim', 40 , 2001 ,'novel', 0),
(4 ,'Lovey Day','George Byron', 20 , 2005 ,'novel', 30),
(5 ,'Old land','Honore Blade', 30 , 2010 ,'Law', 0),
(6 ,'The Battle','Upton Sara', 30 , 1999 ,'medicine', 40),
(7 ,'Rose Hood','Richard haggard', 28 , 2008 ,'cartoon', 28);
# 4、将小说类型(novel)的书的价格都增加 5 。
UPDATE books SET price=price+ 5 WHERE note = 'novel';
# 5、将名称为EmmaT的书的价格改为 40 ,并将说明改为drama。
UPDATE books SET price= 40 ,note='drama' WHERE name='EmmaT';
# 6、删除库存为 0 的记录。
DELETE FROM books WHERE num= 0 ;
# 7、统计书名中包含a字母的书
SELECT * FROM books WHERE name LIKE '%a%';
# 8、统计书名中包含a字母的书的数量和库存总量
SELECT COUNT(*),SUM(num) FROM books WHERE name LIKE '%a%';
# 9、找出“novel”类型的书,按照价格降序排列
SELECT * FROM books WHERE note = 'novel' ORDER BY price DESC;
# 10、查询图书信息,按照库存量降序排列,如果库存量相同的按照note升序排列
SELECT * FROM books ORDER BY num DESC,note ASC;
# 11、按照note分类统计书的数量
SELECT note,COUNT(*) FROM books GROUP BY note;
# 12、按照note分类统计书的库存量,显示库存量超过 30 本的
SELECT note,SUM(num) FROM books GROUP BY note HAVING SUM(num)> 30 ;
# 13、查询所有图书,每页显示 5 本,显示第二页
SELECT * FROM books LIMIT 5 , 5 ;
# 14、按照note分类统计书的库存量,显示库存量最多的
SELECT note,SUM(num) sum_num FROM books GROUP BY note ORDER BY sum_num DESC LIMIT 0 , 1 ;
# 15、查询书名达到 10 个字符的书,不包括里面的空格
SELECT * FROM books WHERE CHAR_LENGTH(REPLACE(name,' ',''))>= 10 ;
/*
16 、查询书名和类型,
其中note值为 novel显示小说,law显示法律,medicine显示医药,cartoon显示卡通,joke显示笑话
*/
SELECT name AS "书名" ,note, CASE note
WHEN 'novel' THEN '小说'
WHEN 'law' THEN '法律'
WHEN 'medicine' THEN '医药'
WHEN 'cartoon' THEN '卡通'
WHEN 'joke' THEN '笑话'
END AS "类型"
FROM books;
# 17、查询书名、库存,其中num值超过 30 本的,显示滞销,大于 0 并低于 10 的,显示畅销,为 0 的显示需要无货
SELECT name,num,CASE
WHEN num> 30 THEN '滞销'
WHEN num> 0 AND num< 10 THEN '畅销'
WHEN num= 0 THEN '无货'
ELSE '正常'
END AS "库存状态"
FROM books;
# 18、统计每一种note的库存量,并合计总量
SELECT IFNULL(note,'合计总库存量') AS note,SUM(num) FROM books GROUP BY note WITH ROLLUP;
# 19、统计每一种note的数量,并合计总量
SELECT IFNULL(note,'合计总数') AS note,COUNT(*) FROM books GROUP BY note WITH ROLLUP;
# 20、统计库存量前三名的图书
SELECT * FROM books ORDER BY num DESC LIMIT 0 , 3 ;
# 21、找出最早出版的一本书
SELECT * FROM books ORDER BY pubdate ASC LIMIT 0 , 1 ;
# 22、找出novel中价格最高的一本书
SELECT * FROM books WHERE note = 'novel' ORDER BY price DESC LIMIT 0 , 1 ;
# 23、找出书名中字数最多的一本书,不含空格
SELECT * FROM books ORDER BY CHAR_LENGTH(REPLACE(name,' ','')) DESC LIMIT 0 ,1;
五、MySQL数据类型精讲
1、MySQL中的数据类型
类型 | 类型举例 |
---|---|
整数类型 | TINYINT、SMALLINT、MEDIUMINT、INT(或INTEGER)、BIGINT |
浮点类型 | FLOAT、DOUBLE |
定点数类型 | DECIMAL |
位类型 | BIT |
日期时间类型 | YEAR、TIME、DATE、DATETIME、TIMESTAMP |
文本字符串类型 | CHAR、VARCHAR、TINYTEXT、TEXT、MEDIUMTEXT、LONGTEXT |
枚举类型 | ENUM |
集合类型 | SET |
二进制字符串类型 | BINARY、VARBINARY、TINYBLOB、BLOB、MEDIUMBLOB、LONGBLOB |
JSON类型 | JSON对象、JSON数组 |
空间数据类型 | 单值类型:GEOMETRY、POINT、LINESTRING、POLYGON; |
集合类型:MULTIPOINT、MULTILINESTRING、MULTIPOLYGON、GEOMETRYCOLLECTION |
常见数据类型的属性,如下
MySQL关键字 | 含义 |
---|---|
NULL | 数据列可包含NULL值 |
NOT NULL | 数据列不允许包含NULL值 |
DEFAULT | 默认值 |
PRIMARY KEY | 主键 |
AUTO_INCREMENT | 自动递增,适用于整数类型 |
UNSIGNED | 无符号 |
CHARACTER SET name | 指定一个字符集 |
2、整数类型
2.1 类型介绍
整数类型 | 字节 | 有符号数取值范围 | 无符号数取值范围 |
---|---|---|---|
TINYINT | 1 | -128 ~ 127 | 0 ~ 255 |
SMALLINT | 2 | -32768 ~ 32767 | 0 ~ 65535 |
MEDIUMINT | 3 | -8388608 ~ 8388607 | 0 ~ 16777215 |
INT、INTEGER | 4 | -2147483648~2147483647 | 0 ~ 4294967295 |
BIGINT | 8 | -9223372036854775808~9223372036854775807 | 0~18446744073709551615 |
2.2 可选参数
整数类型的可选属性有三个
- M: 表示显示宽度,M的取值范围是( 0 , 255 )。例如,int( 5 ):当数据宽度小于 5 位的时候在数字前面需要用字符填满宽度。该项功能需要配合“ZEROFILL”使用,表示用“ 0 ”填满宽度,否则指定显示宽度无效。显示宽度与类型可以存储的值范围无关。 **从MySQL 8. 0. 17 开始,整数数据类型不推荐使用显示宽度属性。**整型数据类型可以在定义表结构时指定所需要的显示宽度,如果不指定,则系统为每一种类型指定默认的宽度值。
- UNSIGNED: 无符号类型(非负),所有的整数类型都有一个可选的属性UNSIGNED(无符号属性),无符号整数类型的最小取值为 0 。所以,如果需要在MySQL数据库中保存非负整数值时,可以将整数类型设置为无符号类型。int类型默认显示宽度为int(11),无符号int类型默认显示宽度为int(10)。
- ZEROFILL: 0填充,(如果某列是ZEROFILL,那么MySQL会自动为当前列添加UNSIGNED属性),如果指定了ZEROFILL只是表示不够M位时,用 0 在左边填充,如果超过M位,只要不超过数据存储范围即可。
在 int(M) 中,M 的值跟 int(M) 所占多少存储空间并无任何关系。 int(3)、int(4)、int(8) 在磁盘上都是占用 4 bytes 的存储空间。也就是说,**int(M),必须和UNSIGNED ZEROFILL一起使用才有意义。**如果整数值超过M位,就按照实际位数存储。只是无须再用字符 0 进行填充。
2.3 适用场景
TINYINT:一般用于枚举数据,比如系统设定取值范围很小且固定的场景
SMALLINT:可以用于较小范围的统计数据,比如统计工厂的固定资产库存数量等
MEDIUMINT:用于较大整数的计算,比如车站每日的客流量等
INT、INTEGER:取值范围足够大,一般情况下不用考虑超限问题,用得最多。比如商品编号
BIGINT:只有当你处理特别巨大的整数时才会用到。比如双十一的交易量、大型门户网站点击量、证券公司衍生产品持仓等。
3、浮点类型
MySQL支持的浮点数类型,分别是 FLOAT、DOUBLE、REAL(REAL默认就是 DOUBLE。如果你把 SQL 模式设定为启用“REAL_AS_FLOAT”,那 么,MySQL 就认为REAL 是 FLOAT)
3.1 数据精度说明
对于浮点类型,在MySQL中单精度值使用 4 个字节,双精度值使用 8 个字节。
- MySQL允许使用 非标准语法 (其他数据库未必支持,因此如果涉及到数据迁移,则最好不要这么 用): FLOAT(M,D) 或 DOUBLE(M,D) 。这里,M称为 精度 ,D称为 标度 。(M,D)中 M=整数位+小数 位,D=小数位。 D<=M<=255,0<=D<=30。例如,定义为FLOAT(5,2)的一个列可以显示为-999.99-999.99。如果超过这个范围会报错。
- FLOAT和DOUBLE类型在不指定(M,D)时,默认会按照实际的精度(由实际的硬件和操作系统决定) 来显示。
- 说明:浮点类型,也可以加 UNSIGNED ,但是不会改变数据范围,例如:FLOAT(3,2) UNSIGNED仍然 只能表示0-9.99的范围。
- 不管是否显式设置了精度(M,D),这里MySQL的处理方案如下:
- 如果存储时,整数部分超出了范围,MySQL就会报错,不允许存这样的值
- 如果存储时,小数点部分若超出范围,就分以下情况:
- 若四舍五入后,整数部分没有超出范围,则只警告,但能成功操作并四舍五入删除多余 的小数位后保存。例如在FLOAT(5,2)列内插入999.009,近似结果是999.01。
- 若四舍五入后,整数部分超出范围,则MySQL报错,并拒绝处理。如FLOAT(5,2)列内插入 999.995和-999.995都会报错。
- 从MySQL 8.0.17开始,FLOAT(M,D) 和DOUBLE(M,D)用法在官方文档中已经明确不推荐使用,将来可 能被移除。另外,关于浮点型FLOAT和DOUBLE的UNSIGNED也不推荐使用了,将来也可能被移除。
3.2 精度误差说明
在编程中,如果用到浮点数,要特别注意误差问题(例如0. 47 + 0. 44 + 0. 19),**因为浮点数是不准确的,所以我们要避免使用“=”来判断两个数是否相等。**同时,在一些对精确度要求较高的项目中,千万不要使用浮点数,不然会导致结果错误,甚至是造成不可挽回的损失。那么,MySQL 有没有精准的数据类型呢?当然有,这就是定点数类型:DECIMAL
4、定点数类型
- MySQL中的定点数类型只有 DECIMAL 一种类型。使用 DECIMAL(M,D) 的方式表示高精度小数。其中,M被称为精度,D被称为标度。0<=M<=65,0<=D<=30,D<M。例如,定义DECIMAL(5,2)的类型,表示该列取值范围是-999.99~999.99。
- DECIMAL(M,D)的最大取值范围与DOUBLE类型一样 ,但是有效的数据范围是由M和D决定的。
DECIMAL 的存储空间并不是固定的,由精度值M决定,总共占用的存储空间为M+2个字节。也就是说,在一些对精度要求不高的场景下,比起占用同样字节长度的定点数,浮点数表达的数值范围可以更大一些。 - 定点数在MySQL内部是以字符串的形式进行存储,这就决定了它一定是精准的。
- 当DECIMAL类型不指定精度和标度时,其默认为DECIMAL(10,0)。当数据的精度超出了定点数类型的精度范围时,则MySQL同样会进行四舍五入处理。
- 浮点数 vs 定点数
- 浮点数相对于定点数的优点是在长度一定的情况下,浮点类型取值范围大,但是不精准,适用于需要取值范围大,又可以容忍微小误差的科学计算场景(比如计算化学、分子建模、流体动力学等)
- 定点数类型取值范围相对小,但是精准,没有误差,适合于对精度要求极高的场景 (比如涉及金额计算的场景)
5、位类型:BIT
BIT类型中存储的是二进制值,类似 010110 。BIT类型,如果没有指定(M),默认是 1 位。这个 1 位,表示只能存 1 位的二进制值。这里(M)是表示二进制的位数,位数最小值为 1 ,最大值为 64 。
CREATE TABLE test_bit1(
f1 BIT,
f2 BIT( 5 ),
f3 BIT( 64 )
);
INSERT INTO test_bit1(f1)
VALUES( 1 );
#Data too long for column 'f1' at row 1
INSERT INTO test_bit1(f1)
VALUES( 2 );
INSERT INTO test_bit1(f2)
VALUES( 23 );
SELECT BIN(f2),HEX(f2) FROM test_bit1;
-- 使用b+ 0 查询数据时,可以直接查询出存储的十进制数据的值
6、日期与时间类型
类型 | 名称 | 字节 | 日期格式 | 最小值 | 最大值 |
---|---|---|---|---|---|
YEAR | 年 | 1 | YYYY或YY | 1901 | 2155 |
TIME | 时间 | 3 | HH:MM:SS | - 838 : 59 : 59 | 838 : 59 : 59 |
DATE | 日期 | 3 | YYYY-MM-DD | 1000 - 01 - 01 | 9999 - 12 - 03 |
DATETIME | 日期 | ||||
时间 | 8 | YYYY-MM-DD | |||
HH:MM:SS | 1000 - 01 - 01 | ||||
00 : 00 : 00 | 9999 - 12 - 31 | ||||
23 : 59 : 59 | |||||
TIMESTAMP | 日期 | ||||
时间 | 4 | YYYY-MM-DD | |||
HH:MM:SS | 1970 - 01 - 01 | ||||
00 : 00 : 00 UTC | 2038 - 01 - 19 | ||||
03 : 14 : 07 UTC |
6.1 YEAR类型
YEAR类型用来表示年份,在所有的日期时间类型中所占用的存储空间最小,只需要 1 个字节的存储空间。在MySQL中,YEAR有以下几种存储格式:
- 以 4 位字符串或数字格式表示YEAR类型,其格式为YYYY,最小值为 1901 ,最大值为2155。
- 以 2 位字符串格式表示YEAR类型,最小值为 00 ,最大值为 99 。
- 当取值为 01 到 69 时,表示 2001 到 2069 ;
- 当取值为 70 到 99 时,表示 1970 到 1999 ;
- 当取值整数的 0 或 00 添加的话,那么是 0000 年;
- 当取值是日期/字符串的 ‘0’ 添加的话,是 2000 年。
从MySQL 5. 5. 27 开始, 2 位格式的YEAR已经不推荐使用 。YEAR默认格式就是“YYYY”,没必要写成YEAR( 4 ),从MySQL 8. 0. 19 开始,不推荐使用指定显示宽度的YEAR( 4 )数据类型。
6.2 DATE类型
DATE类型表示日期,没有时间部分,格式为YYYY-MM-DD,其中,YYYY表示年份,MM表示月份,DD表示日期。需要 3 个字节的存储空间。在向DATE类型的字段插入数据时,同样需要满足一定的格式条件。
- 以YYYY-MM-DD格式或者YYYYMMDD格式表示的字符串日期,其最小取值为 1000-01-01 ,最大取值为9999-12-03 。YYYYMMDD格式会被转化为YYYY-MM-DD格式
- 以YY-MM-DD格式或者YYMMDD格式表示的字符串日期,此格式中,年份为两位数值或字符串满足YEAR类型的格式条件为:当年份取值为 00 到 69 时,会被转化为 2000 到 2069 ;当年份取值为70 到 99时,会被转化为 1970 到 1999
- 使用CURRENT_DATE()或者NOW()函数,会插入当前系统的日期
6.3 TIME类型
TIME类型用来表示时间,不包含日期部分。在MySQL中,需要 3 个字节的存储空间来存储TIME类型的数据,可以使用“HH:MM:SS”格式来表示TIME类型,其中,HH表示小时,MM表示分钟,SS表示秒。
在MySQL中,向TIME类型的字段插入数据时,也可以使用几种不同的格式。
- 可以使用带有冒号的字符串,比如’D HH:MM:SS’、‘HH:MM:SS’、‘HH:MM’、‘D HH:MM’、'D HH’或’SS’格式,都能被正确地插入TIME类型的字段中。其中D表示天,其最小值为0,最大值为34。如果使用带有D格式的字符串插入TIME类型的字段时,D会被转化为小时,计算格式为D* 24 +HH。当使用带有冒号并且不带D的字符串表示时间时,表示当天的时间,比如12 : 10表示12:10:00,而不是00 :12 : 10
- 可以使用不带有冒号的字符串或者数字,格式为’HHMMSS’或者’HHMMSS’。如果插入一个不合法的字符串或者数字,MySQL在存储数据时,会将其自动转化为 00 : 00 : 00 进行存储。比如1210,MySQL会将最右边的两位解析成秒,表示00 : 12 : 10 ,而不是 12 : 10 : 00
- 使用CURRENT_TIME()或者NOW(),会插入当前系统的时间
6.4 DATETIME类型
DATETIME类型在所有的日期时间类型中占用的存储空间最大,总共需要 8 个字节的存储空间。在格式上为DATE类型和TIME类型的组合,可以表示为YYYY-MM-DD HH:MM:SS,其中YYYY表示年份,MM表示月份,DD表示日期,HH表示小时,MM表示分钟,SS表示秒。在向DATETIME类型的字段插入数据时,同样需要满足一定的格式条件
- 以YYYY-MM-DD HH:MM:SS格式或者YYYYMMDDHHMMSS格式的字符串插入DATETIME类型的字段时,最小值为 1000-01-01 00:00:00 ,最大值为 9999-12-03 23:59:59。以YYYYMMDDHHMMSS格式的数字插入DATETIME类型的字段时,会被转化为YYYY-MM-DD HH:MM:SS格式。
- 以YY-MM-DD HH:MM:SS格式或者YYMMDDHHMMSS格式的字符串插入DATETIME类型的字段时,两位数的年份规则符合YEAR类型的规则, 00 到 69 表示 2000 到 2069 ;70 到 99 表示 1970 到 1999
- 使用函数CURRENT_TIMESTAMP()和NOW(),可以向DATETIME类型的字段插入系统的当前日期和时间
6.5 TIMESTAMP类型
TIMESTAMP类型也可以表示日期时间,其显示格式与DATETIME类型相同,都是YYYY-MM-DD HH:MM:SS,需要 4 个字节的存储空间。但是TIMESTAMP存储的时间范围比DATETIME要小很多,只能存储“ 1970 - 01 - 01 00 : 00 : 01 UTC”到“ 2038 - 01 - 19 03 : 14 : 07 UTC”之间的时间。其中,UTC表示世界统一时间,也叫作世界标准时间。
- 存储数据的时候需要对当前时间所在的时区进行转换,查询数据的时候再将时间转换回当前的时区。因此,使用TIMESTAMP存储的同一个时间值,在不同的时区查询时会显示不同的时间。
向TIMESTAMP类型的字段插入数据时,当插入的数据格式满足YY-MM-DD HH:MM:SS和YYMMDDHHMMSS时,两位数值的年份同样符合YEAR类型的规则条件,只不过表示的时间范围要小很多。如果向TIMESTAMP类型的字段插入的时间超出了TIMESTAMP类型的范围,则MySQL会抛出错误信息。
TIMESTAMP和DATETIME的区别:
- TIMESTAMP存储空间比较小,表示的日期时间范围也比较小
- 底层存储方式不同,TIMESTAMP底层存储的是毫秒值,距离1970-1-1 0:0:0 0 毫秒的毫秒值。
- 两个日期比较大小或日期计算时,TIMESTAMP更方便、更快。
- TIMESTAMP和时区有关。TIMESTAMP会根据用户的时区不同,显示不同的结果。而DATETIME则只能反映出插入时当地的时区,其他时区的人查看数据必然会有误差的
-- 一般存注册时间、商品发布时间等,不建议使用DATETIME存储,而是使用时间戳,其他建议datetime
SELECT UNIX_TIMESTAMP();
6.6 TIMESTAMPDIFF函数
比较两个时间的差值,举例SELECT TIMESTAMPDIFF(SECOND, '2023-05-30 19:57:18', '2023-05-30 20:05:00');
second部分可以替换程minute/hour/day/month/year
7、文本字符串类型
MySQL中,文本字符串总体上分为CHAR、VARCHAR、TINYTEXT、TEXT、MEDIUMTEXT、
LONGTEXT、ENUM、SET等类型
7.1 CHAR与VARCHAR类型
字符串(文本)类型 | 特点 | 长度 | 长度范围 | 占用的存储空间 |
---|---|---|---|---|
CHAR(M) | 固定长度 | M | 0 <= M <= 255 | M个字节 |
VARCHAR(M) | 可变长度 | M | 0 <= M <= 65535 | (实际长度 + 1 ) 个字节 |
CHAR类型:
- CHAR(M) 类型一般需要预先定义字符串长度。如果不指定(M),则表示长度默认是 1 个字符。
- 如果保存时,数据的实际长度比CHAR类型声明的长度小,则会在右侧填充空格以达到指定的长度。当MySQL检索CHAR类型的数据时,CHAR类型的字段会去除尾部的空格。
- 定义CHAR类型字段时,声明的字段长度即为CHAR类型字段所占的存储空间的字节数。
VARCHAR类型:
- VARCHAR(M) 定义时,必须指定长度M,否则报错。
- MySQL4.0版本以下,varchar(20):指的是 20 字节,如果存放UTF8汉字时,只能存 6 个(每个汉字 3 字节) ;MySQL5.0版本以上,varchar(20):指的是 20 字符。
- 检索VARCHAR类型的字段数据时,会保留数据尾部的空格。VARCHAR类型的字段所占用的存储空间为字符串实际长度加 1 个字节。
哪些情况使用 CHAR 或 VARCHAR 更好
情况 1 :存储很短的信息。比如门牌号码 101 ,201……这样很短的信息应该用char,因为varchar还要占个byte用于存储信息长度,本来打算节约存储的,结果得不偿失。
情况 2 :固定长度的。比如使用uuid作为主键,那用char应该更合适。因为他固定长度,varchar动态根据长度的特性就消失了,而且还要占个长度信息。
情况 3 :十分频繁改变的column。因为varchar每次存储都要有额外的计算,得到长度等工作,如果一个非常频繁改变的,那就要有很多的精力用于计算,而这些对于char来说是不需要的。
情况 4 :具体存储引擎中的情况:
- MyISAM 数据存储引擎和数据列:MyISAM数据表,最好使用固定长度(CHAR)的数据列代替可变长度(VARCHAR)的数据列。这样使得整个表静态化,从而使数据检索更快,用空间换时间。
- MEMORY 存储引擎和数据列:MEMORY数据表目前都使用固定长度的数据行存储,因此无论使用CHAR或VARCHAR列都没有关系,两者都是作为CHAR类型处理的。
- InnoDB存储引擎,建议使用VARCHAR类型。因为对于InnoDB数据表,内部的行存储格式并没有区分固定长度和可变长度列(所有数据行都使用指向数据列值的头指针),而且 主要影响性能的因素是数据行使用的存储总量 ,由于char平均占用的空间多于varchar,所以除了简短并且固定长度的,其他考虑varchar。这样节省空间,对磁盘I/O和数据存储总量比较好。
7.2 TEXT类型
在MySQL中,TEXT用来保存文本类型的字符串,总共包含 4 种类型,分别为TINYTEXT、TEXT、MEDIUMTEXT 和 LONGTEXT 类型。在向TEXT类型的字段保存和查询数据时,系统自动按照实际长度存储,不需要预先定义长度。这一点和VARCHAR类型相同
文本字符串类型 | 特点 | 长度 | 长度范围 | 占用的存储空间 |
---|---|---|---|---|
TINYTEXT | 小文本、可变长度 | L | 0 <= L <= 255 | L + 2 个字节 |
TEXT | 文本、可变长度 | L | 0 <= L <= 65535 | L + 2 个字节 |
MEDIUMTEXT | 中等文本、可变长度 | L | 0 <= L <= 16777215 | L + 3 个字节 |
LONGTEXT | 大文本、可变长度 | L | 0 <= L<= 4294967295(相当于4GB) | L + 4 个字节 |
由于实际存储的长度不确定,MySQL 不允许 TEXT 类型的字段做主键 。遇到这种情况,你只能采用CHAR(M),或者 VARCHAR(M)。另外TEXT文本类型,可以存比较大的文本段,搜索速度稍慢,因此如果不是特别大的内容,建议使用CHAR,VARCHAR来代替。还有TEXT类型不用加默认值,加了也没用。而且text和blob类型的数据删除后容易导致“空洞”,使得文件碎片比较多,所以频繁使用的表不建议包含TEXT类型字段,建议单独分出去,单独用一个表
7.3 ENUM类型
ENUM类型也叫作枚举类型,ENUM类型的取值范围需要在定义字段时进行指定。设置字段值时,ENUM类型只允许从成员中选取单个值,不能一次选取多个值
文本字符串类型 | 长度 | 长度范围 | 占用的存储空间 |
---|---|---|---|
ENUM | L | 1 <= L <= 65535 | 1 或 2 个字节 |
- 当ENUM类型包含 1 ~ 255 个成员时,需要 1 个字节的存储空间;
- 当ENUM类型包含 256 ~ 65535 个成员时,需要 2 个字节的存储空间
- ENUM类型的成员个数的上限为 65535 个
CREATE TABLE test_enum(
season ENUM('春','夏','秋','冬','unknow')
);
INSERT INTO test_enum
VALUES('春'),('秋');
# 忽略大小写
INSERT INTO test_enum
VALUES('UNKNOW');
# 允许按照角标的方式获取指定索引位置的枚举值
INSERT INTO test_enum
VALUES('1'),( 3 );
# Data truncated for column 'season' at row 1
INSERT INTO test_enum
VALUES('ab');
# 当ENUM类型的字段没有声明为NOT NULL时,插入NULL也是有效的
INSERT INTO test_enum
VALUES(NULL);
7.4 SET类型
SET表示一个字符串对象,可以包含 0 个或多个成员,但成员个数的上限为 64 。设置字段值时,可以取取值范围内的 0 个或多个值。当SET类型包含的成员个数不同时,其所占用的存储空间也是不同的
成员个数范围(L表示实际成员个数) | 占用的存储空间 |
---|---|
1 <= L <= 8 | 1 个字节 |
9 <= L <= 16 | 2 个字节 |
17 <= L <= 24 | 3 个字节 |
25 <= L <= 32 | 4 个字节 |
33 <= L <= 64 | 8 个字节 |
SET类型在存储数据时成员个数越多,其占用的存储空间越大。注意:SET类型在选取成员时,可以一次选择多个成员,这一点与ENUM类型不同。
CREATE TABLE temp_mul(
gender ENUM('男','女'),
hobby SET('吃饭','睡觉','打豆豆','写代码')
);
INSERT INTO temp_mul VALUES('男','睡觉,打豆豆'); #成功
# Data truncated for column 'gender' at row 1
INSERT INTO temp_mul VALUES('男,女','睡觉,写代码'); #失败
# Data truncated for column 'gender' at row 1
INSERT INTO temp_mul VALUES('妖','睡觉,写代码');#失败
INSERT INTO temp_mul VALUES('男','睡觉,写代码,吃饭'); #成功
8、二进制于JSON
8.1 二进制字符串类型
MySQL中的二进制字符串类型主要存储一些二进制数据,比如可以存储图片、音频和视频等二进制数据。MySQL中支持的二进制字符串类型主要包括BINARY、VARBINARY、TINYBLOB、BLOB、MEDIUMBLOB 和LONGBLOB类型。
BINARY与VARBINARY类型
BINARY和VARBINARY类似于CHAR和VARCHAR,只是它们存储的是二进制字符串。BINARY (M)为固定长度的二进制字符串,M表示最多能存储的字节数,取值范围是 0 ~ 255 个字符。如果未指定(M),表示只能存储 1 个字节。例如BINARY ( 8 ),表示最多能存储 8 个字节,如果字段值不足(M)个字节,将在右边填充’\ 0’以补齐指定长度。VARBINARY (M)为可变长度的二进制字符串,M表示最多能存储的字节数,总字节数不能超过行的字节长度限制 65535 ,另外还要考虑额外字节开销,VARBINARY类型的数据除了存储数据本身外,还需要 1 或 2 个字节来存储数据的字节数。VARBINARY类型必须指定(M),否则报错。
二进制字符串类型 | 特点 | 值的长度 | 占用空间 |
---|---|---|---|
BINARY(M) | 固定长度 | M( 0 <= M <= 255 ) | M个字节 |
VARBINARY(M) | 可变长度 | M( 0 <= M <= 65535 ) | M+ 1 个字节 |
BLOB类型
BLOB是一个二进制大对象,可以容纳可变数量的数据。MySQL中的BLOB类型包括TINYBLOB、BLOB、MEDIUMBLOB和LONGBLOB 4种类型,它们可容纳值的最大长度不同。可以存储一个二进制的大对象,比如图片、音频和视频等。需要注意的是,在实际工作中,往往不会在MySQL数据库中使用BLOB类型存储大对象数据,通常会将图片、音频和视频文件存储到服务器的磁盘上,并将图片、音频和视频的访问路径存储到MySQL中。
二进制字符串类型 | 值的长度 | 长度范围 | 占用空间 |
---|---|---|---|
TINYBLOB | L | 0 <= L <= 255 | L + 1 个字节 |
BLOB | L | 0 <= L <= 65535(相当于64KB) | L + 2 个字节 |
MEDIUMBLOB | L | 0 <= L <= 16777215 (相当于16MB) | L + 3 个字节 |
LONGBLOB | L | 0 <= L <= 4294967295(相当于4GB) | L + 4 个字节 |
TEXT和BLOB的使用注意事项
在使用text和blob字段类型时要注意以下几点,以便更好的发挥数据库的性能。
- BLOB和TEXT值也会引起自己的一些问题,特别是执行了大量的删除或更新操作的时候。删除这种值会在数据表中留下很大的”空洞“,以后填入这些”空洞”的记录可能长度不同。为了提高性能,建议定期使用 OPTIMIZE TABLE 功能对这类表进行碎片整理。
- 如果需要对大文本字段进行模糊查询,MySQL 提供了前缀索引。但是仍然要在不必要的时候避免检索大型的BLOB或TEXT值。例如,SELECT * 查询就不是很好的想法,除非你能够确定作为约束条件的WHERE子句只会找到所需要的数据行。否则,你可能毫无目的地在网络上传输大量的值。
- 把BLOB或TEXT列分离到单独的表中。在某些环境中,如果把这些数据列移动到第二张数据表中,可以让你把原数据表中的数据列转换为固定长度的数据行格式,那么它就是有意义的。这会减少主表中的碎片,使你得到固定长度数据行的性能优势。它还使你在主数据表上运行 SELECT * 查询的时候不会通过网络传输大量的BLOB或TEXT值
8.2 JSON类型
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。它易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。 JSON 可以将 JavaScript 对象中表示的一组数据转换为字符串,然后就可以在网络或者程序之间轻松地传递这个字符串,并在需要的时候将它还原为各编程语言所支持的数据格式。
在MySQL 5.7中,就已经支持JSON数据类型。在MySQL 8.x版本中,JSON类型提供了可以进行自动验证的JSON文档和优化的存储结构,使得在MySQL中存储和读取JSON类型的数据更加方便和高效
CREATE TABLE test_json(
js json
);
INSERT INTO test_json (js)
VALUES ('{"name":"songhk", "age":18, "address":{"province":"beijing",
"city":"beijing"}}');
-- 查询
SELECT * FROM test_json;
-- 当需要检索JSON类型的字段中数据的某个具体值时,可以使用“->”和“->>”符号
SELECT js -> '$.name' AS NAME,js -> '$.age' AS age ,js -> '$.address.province' AS province, js -> '$.address.city' AS city -> FROM test_json;
8.3 空间类型
MySQL 空间类型扩展支持地理特征的生成、存储和分析。这里的地理特征表示世界上具有位置的任何东西,可以是一个实体,例如一座山;可以是空间,例如一座办公楼;也可以是一个可定义的位置,例如一个十字路口等等。MySQL中使用Geometry(几何)来表示所有地理特征。Geometry指一个点或点的集合,代表世界上任何具有位置的事物。
六、约束
1、约束(constraint)概述
1.1 为什么需要约束
数据完整性(Data Integrity)是指数据的精确性(Accuracy)和可靠性(Reliability)。它是防止数据库中存在不符合语义规定的数据和防止因错误信息的输入输出造成无效操作或错误信息而提出的。为了保证数据的完整性,SQL规范以约束的方式对表数据进行额外的条件限制。从以下四个方面考虑:
- 实体完整性(Entity Integrity):例如,同一个表中,不能存在两条完全相同无法区分的记录
- 域完整性(Domain Integrity):例如:年龄范围0-120,性别范围“男/女”
- 引用完整性(Referential Integrity):例如:员工所在部门,在部门表中要能找到这个部门
- 用户自定义完整性(User-defined Integrity):例如:用户名唯一、密码不能为空等,本部门经理的工资不得高于本部门职工的平均工资的 5 倍。
1.2 什么是约束
约束是表级的强制规定。可以在 创建表时规定约束(通过 CREATE TABLE 语句) ,或者在 表创建之后通过 ALTER TABLE 语句规定约束
1.3 约束的分类
- 根据约束数据列的限制, 约束可分为:
- 单列约束 :每个约束只约束一列
- 多列约束 :每个约束可约束多列数据
- 根据约束的作用范围 ,约束可分为:
- 列级约束 :只能作用在一个列上,跟在列的定义后面
- 表级约束 :可以作用在多个列上,不与列一起,而是单独定义
- 根据约束起的作用 ,约束可分为:
- NOT NULL 非空约束,规定某个字段不能为空
- UNIQUE 唯一约束 , 规定某个字段在整个表中是唯一的
- PRIMARY KEY 主键(非空且唯一)约束
- FOREIGN KEY 外键约束
- CHECK 检查约束
- DEFAULT 默认值约束
-- 注意: MySQL不支持check约束,但可以使用check约束,而没有任何效果
-- information_schema数据库名(系统库)
-- table_constraints表名称(专门存储各个表的约束)
SELECT * FROM information_schema.table_constraints
WHERE table_name = '表名称';
2、非空约束
NOT NULL,限定某个字段/某列的值不允许为空
- 默认,所有的类型的值都可以是NULL,包括INT、FLOAT等数据类型
- 非空约束只能出现在表对象的列上,只能某个列单独限定非空,不能组合非空
- 一个表可以有很多列都分别限定了非空
- 空字符串’'不等于NULL, 0 也不等于NULL
CREATE TABLE 表名称(
字段名 数据类型,
字段名 数据类型 NOT NULL,
字段名 数据类型 NOT NULL
);
-- 建表后修改
alter table 表名称 modify 字段名 数据类型 not null;
-- 删除非空约束
-- 去掉not null,相当于修改某个非注解字段,该字段允许为空
alter table 表名称 modify 字段名 数据类型 NULL;
-- 去掉not null,相当于修改某个非注解字段,该字段允许为空
alter table 表名称 modify 字段名 数据类型;
3、唯一性约束
UNIQUE,用来限制某个字段/某列的值不能重复。
- 同一个表可以有多个唯一约束。
- 唯一约束可以是某一个列的值唯一,也可以多个列组合的值唯一。
- 唯一性约束允许列值为空。
- 在创建唯一约束的时候,如果不给唯一约束命名,就默认和列名相同。
- MySQL会给唯一约束的列上默认创建一个唯一索引。
-- 建表
create table 表名称(
字段名 数据类型,
字段名 数据类型 unique,
字段名 数据类型 unique key,
字段名 数据类型
);
create table 表名称(
字段名 数据类型,
字段名 数据类型,
字段名 数据类型,
[constraint 约束名] unique key(字段名)
);
-- 建表后指定唯一键约束
alter table 表名称 add unique key(字段列表);
alter table 表名称 modify 字段名 字段类型 unique;
-- 关于复合唯一约束,例如
create table student_course(
id int,
sid int,
cid int,
score int,
unique key(sid,cid) #复合唯一
);
-- 删除约束
-- 添加唯一性约束的列上也会自动创建唯一索引
-- 删除唯一约束只能通过删除唯一索引的方式删除
-- 删除时需要指定唯一索引名,唯一索引名就和唯一约束名一样
-- 如果创建唯一约束时未指定名称,如果是单列,就默认和列名相同;如果是组合列,那么默认和()中排在第一个的列名相同。也可以自定义唯一性约束名
-- 查看都有哪些约束
SELECT * FROM information_schema.table_constraints WHERE table_name = '表名';
ALTER TABLE USER DROP INDEX uk_name_pwd;
-- 可以通过 show index from 表名称;查看表的索引
4、PRIMARY KEY 约束
primary key,用来唯一标识表中的一行记录
- 一个表最多只能有一个主键约束,建立主键约束可以在列级别创建,也可以在表级别上创建。
- 主键约束对应着表中的一列或者多列(复合主键)
- 如果是多列组合的复合主键约束,那么这些列都不允许为空值,并且组合的值不允许重复。
- MySQL的主键名总是PRIMARY ,就算自己命名了主键约束名也没用。
- 当创建主键约束时,系统默认会在所在的列或列组合上建立对应的 主键索引 (能够根据主键查询的,就根据主键查询,效率更高)。如果删除主键约束了,主键约束对应的索引就自动删除了。
- 需要注意的一点是,不要修改主键字段的值。因为主键是数据记录的唯一标识,如果修改了主键的值,就有可能会破坏数据的完整性。
-- 创建
create table 表名称(
字段名 数据类型 primary key, --列级模式
字段名 数据类型,
字段名 数据类型
);
create table 表名称(
字段名 数据类型,
字段名 数据类型,
字段名 数据类型,
[constraint 约束名] primary key(字段名) --表级模式
);
-- 建表后增加主键约束
-- 字段列表可以是一个字段,也可以是多个字段,如果是多个字段的话,是复合主键
ALTER TABLE 表名称 ADD PRIMARY KEY(字段列表);
-- 举例
ALTER TABLE emp5 ADD PRIMARY KEY(NAME,pwd);
-- 删除主键约束
alter table 表名称 drop primary key;
-- 删除主键约束,不需要指定主键名,因为一个表只有一个主键,删除主键约束后,非空还存在
5、AUTO_INCREMENT
auto_increment,某个字段的值自增
- 一个表最多只能有一个自增长列
- 当需要产生唯一标识符或顺序值时,可设置自增长
- 自增长列约束的列必须是键列(主键列,唯一键列)
- 自增约束的列的数据类型必须是整数类型
- 如果自增列指定了 0 和 null,会在当前最大值的基础上自增;如果自增列手动指定了具体值,直接赋值为具体值
-- 建表时
create table 表名称(
字段名 数据类型 primary key auto_increment,
字段名 数据类型 unique key not null,
字段名 数据类型 unique key,
字段名 数据类型 not null default 默认值,
);
-- 自增从n开始,也可以自定义步长
create table 表名称(
字段名 数据类型 default 默认值 ,
字段名 数据类型 unique key auto_increment,
字段名 数据类型 not null default 默认值,
primary key(字段名)
) auto_increment = n;
-- 修改
alter table 表名称 modify 字段名 数据类型 auto_increment;
-- 给这个字段增加自增约束
alter table 表名称 modify 字段名 数据类型 auto_increment
-- 去掉auto_increment相当于删除
alter table 表名称 modify 字段名 数据类型;
在MySQL 8.0之前,自增主键AUTO_INCREMENT的值如果大于max(primary key)+1,在MySQL重启后,会重置AUTO_INCREMENT=max(primary key)+1,这种现象在某些情况下会导致业务主键冲突或者其他难以发现的问题。
MySQL 8. 0 将自增主键的计数器持久化到重做日志中。每次计数器发生改变,都会将其写入重做日志中。如果数据库重启,InnoDB会根据重做日志中的信息来初始化计数器的内存值
6、FOREIGN KEY 约束(不推荐)
6.1 作用和特点
限定某个表的某个字段的引用完整性
- 主表(父表):被引用的表,被参考的表
- 从表(子表):引用别人的表,参考别人的表
例如:员工表的员工所在部门这个字段的值要参考部门表:部门表是主表,员工表是从表。例如:学生表、课程表、选课表:选课表的学生和课程要分别参考学生表和课程表,学生表和课程表是主表,选课表是从表。
- 从表的外键列,必须引用/参考主表的主键或唯一约束的列为什么?因为被依赖/被参考的值必须是唯一的
- 在创建外键约束时,如果不给外键约束命名,** 默认名不是列名,而是自动产生一个外键名** (例如student_ibfk_1;),也可以指定外键约束名。
- 创建(CREATE)表时就指定外键约束的话,先创建主表,再创建从表
- 删表时,先删从表(或先删除外键约束),再删除主表
- 当主表的记录被从表参照时,主表的记录将不允许删除,如果要删除数据,需要先删除从表中依赖该记录的数据,然后才可以删除主表的数据
- 在“从表”中指定外键约束,并且一个表可以建立多个外键约束
- 从表的外键列与主表被参照的列名字可以不相同,但是数据类型必须一样,逻辑意义一致。如果类型不一样,创建子表时,就会出现错误“ERROR 1005 (HY000): Can’t create table’database.tablename’(errno: 150)”。例如:都是表示部门编号,都是int类型。
- 当创建外键约束时,系统默认会在所在的列上建立对应的普通索引 。但是索引名是外键的约束名。(根据外键查询效率很高)
- 删除外键约束后,必须手动删除对应的索引
6.2 简单演示
-- 建表时
create table 主表名称(
字段 1 数据类型 primary key,
字段 2 数据类型
);
create table 从表名称(
字段 1 数据类型 primary key,
字段 2 数据类型,
[CONSTRAINT <外键约束名称>] FOREIGN KEY(从表的某个字段) references 主表名(被参考字段)
);
-- (从表的某个字段)的数据类型必须与主表名(被参考字段)的数据类型一致,逻辑意义也一样
-- (从表的某个字段)的字段名可以与主表名(被参考字段)的字段名一样,也可以不一样
-- FOREIGN KEY: 在表级指定子表中的列
-- REFERENCES: 标示在父表中的列
-- 建表后
ALTER TABLE 从表名 ADD [CONSTRAINT 约束名] FOREIGN KEY (从表的字段) REFERENCES 主表名(被引用字段) [on update xx][on delete xx];
- 添加了外键约束后,主表的修改和删除数据受约束
- 添加了外键约束后,从表的添加和修改数据受约束
- 在从表上建立外键,要求主表必须存在
- 删除主表时,要求从表从表先删除,或将从表中外键引用该主表的关系先删除
6.3 约束等级
- Cascade方式:在父表上update/delete记录时,同步update/delete掉子表的匹配记录
- Set null方式:在父表上update/delete记录时,将子表上匹配记录的列设为null,但是要注意子表的外键列不能为not null
- No action方式:如果子表中有匹配的记录,则不允许对父表对应候选键进行update/delete操作
- Restrict方式:同no action, 都是立即检查外键约束
- Set default方式(在可视化工具SQLyog中可能显示空白):父表有变更时,子表将外键列设置成一个默认的值,但Innodb不能识别
对于外键约束,最好是采用: ON UPDATE CASCADE ON DELETE RESTRICT 的方式。
-- 举例
create table dept(
did int primary key, #部门编号
dname varchar( 50 ) #部门名称
);
create table emp(
eid int primary key, #员工编号
ename varchar( 5 ), #员工姓名
deptid int, #员工所在的部门
foreign key (deptid) references dept(did) on update cascade on delete set null
-- 把修改操作设置为级联修改等级,把删除操作设置为set null等级
);
6.4 删除外键约束
-- 第一步先查看约束名和删除外键约束
SELECT * FROM information_schema.table_constraints WHERE table_name = '表名称';
ALTER TABLE 从表名 DROP FOREIGN KEY 外键约束名;
-- 第二步查看索引名和删除索引。(注意,只能手动删除)
SHOW INDEX FROM 表名称;
ALTER TABLE 从表名 DROP INDEX 索引名;
6.5 开发场景
问题 1 :如果两个表之间有关系(一对一、一对多),比如:员工表和部门表(一对多),它们之间是否一定要建外键约束?
答:不是的
问题 2 :建和不建外键约束有什么区别?
答:建外键约束,你的操作(创建表、删除表、添加、修改、删除)会受到限制,从语法层面受到限制。例如:在员工表中不可能添加一个员工信息,它的部门的值在部门表中找不到。不建外键约束,你的操作(创建表、删除表、添加、修改、删除)不受限制,要保证数据的引用完整性,只能依靠程序员的自觉,或者是在Java程序中进行限定。例如:在员工表中,可以添加一个员工的信息,它的部门指定为一个完全不存在的部门。
问题 3 :那么建和不建外键约束和查询有没有关系?
答:没有
在 MySQL 里,外键约束是有成本的,需要消耗系统资源。对于大并发的 SQL 操作,有可能会不适合。比如大型网站的中央数据库,可能会因为外键约束的系统开销而变得非常慢。所以, MySQL 允许你不使用系统自带的外键约束,在应用层面完成检查数据一致性的逻辑。也就是说,即使你不用外键约束,也要想办法通过应用层面的附加逻辑,来实现外键约束的功能,确保数据的一致性。
阿里开发规范
【强制】不得使用外键与级联,一切外键概念必须在应用层解决。
说明:(概念解释)学生表中的 student_id 是主键,那么成绩表中的 student_id 则为外键。如果更新学生表中的 student_id,同时触发成绩表中的 student_id 更新,即为级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风险;外键影响数据库的插入速度。
7、CHECK 约束
CHECK,检查某个字段的值是否符号xx要求,一般指的是值的范围,MySQL5.7 可以使用check约束,但check约束对数据验证没有任何作用。添加数据时,没有任何错误或警告,但是MySQL 8.0中可以使用check约束了
create table employee(
eid int primary key,
ename varchar( 5 ),
gender char check ('男' or '女')
);
-- age tinyint check(age > 20 ) 或 sex char( 2 ) check(sex in('男','女'))
8、DEFAULT约束
给某个字段/某列指定默认值,一旦设置默认值,在插入数据时,如果此字段没有显式赋值,则赋值为默认值。
-- 建表
create table 表名称(
字段名 数据类型 primary key,
字段名 数据类型 unique key not null,
字段名 数据类型 unique key,
字段名 数据类型 not null default 默认值,
);
create table 表名称(
字段名 数据类型 default 默认值 ,
字段名 数据类型 not null default 默认值,
字段名 数据类型 not null default 默认值,
primary key(字段名),
unique key(字段名)
);
-- 说明:默认值约束一般不在唯一键和主键列上加
desc employee;
-- 后期添加
-- 如果这个字段原来有非空约束,你还保留非空约束,那么在加默认值约束时,还得保留非空约束,否则非空约束就被删除了
-- 同理,在给某个字段加非空约束也一样,如果这个字段原来有默认值约束,你想保留,也要在modify语句中保留默认值约束,否则就删除了
alter table 表名称 modify 字段名 数据类型 default 默认值 not null;
alter table 表名称 modify 字段名 数据类型 default 默认值;
-- 删除默认值约束
-- 删除默认值约束,也不保留非空约束
alter table 表名称 modify 字段名 数据类型 ;
-- 删除默认值约束,保留非空约束
alter table 表名称 modify 字段名 数据类型 not null;
七、视图
1、常见的数据库对象
对象 | 描述 |
---|---|
表(TABLE) | 表是存储数据的逻辑单元,以行和列的形式存在,列就是字段,行就是记录 |
数据字典 | 就是系统表,存放数据库相关信息的表。系统表的数据通常由数据库系统维护,程序员通常不应该修改,只可查看 |
约束(CONSTRAINT) | 执行数据校验的规则,用于保证数据完整性的规则 |
视图(VIEW) | 一个或者多个数据表里的数据的逻辑显示,视图并不存储数据 |
索引(INDEX) | 用于提高查询性能,相当于书的目录 |
存储过程(PROCEDURE) | 用于完成一次完整的业务处理,没有返回值,但可通过传出参数将多个值传给调用环境 |
存储函数(FUNCTION) | 用于完成一次特定的计算,具有一个返回值 |
触发器(TRIGGER) | 相当于一个事件监听器,当数据库发生特定事件后,触发器被触发,完成相应的处理 |
2、视图概述
- 视图是一种虚拟表,本身是不具有数据的,占用很少的内存空间,它是 SQL 中的一个重要概念。
- 视图建立在已有表的基础上 , 视图赖以建立的这些表称为 基表
- 视图的创建和删除只影响视图本身,不影响对应的基表。但是当对视图中的数据进行增加、删除和修改操作时,数据表中的数据会相应地发生变化,反之亦然。
- 向视图提供数据内容的语句为 SELECT 语句, 可以将视图理解为 存储起来的 SELECT 语句
- 在数据库中,视图不会保存数据,数据真正保存在数据表中。当对视图中的数据进行增加、删除和修改操作时,数据表中的数据会相应地发生变化;反之亦然。
- 视图,是向用户提供基表数据的另一种表现形式。通常情况下,小型项目的数据库可以不使用视图,但是在大型项目中,以及数据表比较复杂的情况下,视图的价值就凸显出来了,它可以帮助我们把经常查询的结果集放到虚拟表中,提升使用效率。理解和使用起来都非常方便。
3、创建视图
-- 在 CREATE VIEW 语句中嵌入子查询
CREATE [OR REPLACE]
[ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}]
VIEW 视图名称 [(字段列表)]
AS 查询语句
[WITH [CASCADED|LOCAL] CHECK OPTION]
-- 精简版
CREATE VIEW 视图名称 AS 查询语句
-- 举例
CREATE VIEW empvu
AS
SELECT employee_id, last_name, salary
FROM employees WHERE department_id = 80 ;
-- 查询视图
SELECT * FROM salvu80;
-- 实际上就是我们在 SQL 查询语句的基础上封装了视图 VIEW,这样就会基于 SQL 语句的结果集形成一张虚拟表
-- 在创建视图时,没有在视图名后面指定字段列表,则视图中字段列表默认和SELECT语句中的字段列表一致。如果SELECT语句中给字段取了别名,那么视图中的字段名和别名相同
-- 创建多表联合视图
-- 举例
CREATE VIEW dept_sum_vu
(name, minsal, maxsal, avgsal)
AS
SELECT d.department_name, MIN(e.salary), MAX(e.salary),AVG(e.salary)
FROM employees e, departments d
WHERE e.department_id = d.department_id
GROUP BY d.department_name;
-- 利用视图对数据进行格式化
-- 我们经常需要输出某个格式的内容,比如我们想输出员工姓名和对应的部门名,对应格式为emp_name(department_name),就可以使用视图来完成数据格式化的操作
CREATE VIEW emp_depart
AS
SELECT CONCAT(last_name,'(',department_name,')') AS emp_dept
FROM employees e JOIN departments d
WHERE e.department_id = d.department_id
-- 基于视图创建视图
-- 当我们创建好一张视图之后,还可以在它的基础上继续创建视图
CREATE VIEW emp_dept_ysalary
AS
SELECT emp_dept.ename,dname,year_salary
FROM emp_dept INNER JOIN emp_year_salary
ON emp_dept.ename = emp_year_salary.ename;
4、查看视图
-- 查看数据库的表对象、视图对象
SHOW TABLES;
-- 查看视图的结构
DESC/DESCRIBE 视图名称;
-- 查看视图的属性信息
-- 查看视图信息(显示数据表的存储引擎、版本、数据行数和数据大小等)
-- 执行结果显示,注释Comment为VIEW,说明该表为视图,其他的信息为NULL,说明这是一个虚表
SHOW TABLE STATUS LIKE '视图名称'\G
-- 查看视图的详细定义信息
SHOW CREATE VIEW 视图名称;
5、更新视图的数据
5.1 一般情况
MySQL支持使用INSERT、UPDATE和DELETE语句对视图中的数据进行插入、更新和删除操作。当视图中的数据发生变化时,数据表中的数据也会发生变化,反之亦然。
5.2 不可更新的视图
要使视图可更新,视图中的行和底层基本表中的行之间必须存在一对一的关系。另外当视图定义出现如下情况时,视图不支持更新操作:
- 在定义视图的时候指定了“ALGORITHM = TEMPTABLE”,视图将不支持INSERT和DELETE操作;
- 视图中不包含基表中所有被定义为非空又未指定默认值的列,视图将不支持INSERT操作;
- 在定义视图的SELECT语句中使用了JOIN联合查询,视图将不支持INSERT和DELETE操作;
- 在定义视图的SELECT语句后的字段列表中使用了数学表达式或子查询,视图将不支持INSERT,也不支持UPDATE使用了数学表达式、子查询的字段值;
- 在定义视图的SELECT语句后的字段列表中使用DISTINCT、聚合函数、GROUP BY、HAVING、UNION等,视图将不支持INSERT、UPDATE、DELETE;
- 在定义视图的SELECT语句中包含了子查询,而子查询中引用了FROM后面的表,视图将不支持INSERT、UPDATE、DELETE;
- 视图定义基于一个不可更新视图;
- 常量视图
虽然可以更新视图数据,但总的来说,视图作为虚拟表,主要用于方便查询,不建议更新视图的数据。 对视图数据的更改,都是通过对实际数据表里数据的操作来完成的
6、修改、删除视图
-- 修改视图
-- 使用CREATE OR REPLACE VIEW 子句 修改视图
-- 注意CREATE VIEW 子句中各列的别名应和子查询中各列相对应
CREATE OR REPLACE VIEW empvu
(id_number, name, sal, department_id)
AS
SELECT employee_id, first_name || ' ' || last_name, salary, department_id
FROM employees
WHERE department_id = 80 ;
-- 方式2
ALTER VIEW 视图名称
AS
查询语句
-- 删除视图
-- 删除视图只是删除视图的定义,并不会删除基表的数据
DROP VIEW IF EXISTS 视图名称;
DROP VIEW IF EXISTS 视图名称1,视图名称2,视图名称3,...;
-- 基于视图a、b创建了新的视图c,如果将视图a或者视图b删除,会导致视图c的查询失败。这样的视图c需要手动删除或修改,否则影响使用
7、总结
7.1 优点
- 操作简单。将经常使用的查询操作定义为视图,可以使开发人员不需要关心视图对应的数据表的结构、表与表之间的关联关系,也不需要关心数据表之间的业务逻辑和查询条件,而只需要简单地操作视图即可,极大简化了开发人员对数据库的操作。
- 减少数据冗余。视图跟实际数据表不一样,它存储的是查询语句。所以,在使用的时候,我们要通过定义视图的查询语句来获取结果集。而视图本身不存储数据,不占用数据存储的资源,减少了数据冗余。
- 数据安全。MySQL将用户对数据的访问限制在某些数据的结果集上,而这些数据的结果集可以使用视图来实现。用户不必直接查询或操作数据表。这也可以理解为视图具有隔离性。视图相当于在用户和实际的数据表之间加了一层虚拟表。同时,MySQL可以根据权限将用户对数据的访问限制在某些视图上, 用户不需要查询数据表,可以直接通过视图获取数据表中的信息 。这在一定程度上保障了数据表中数据的安全性。
- 适应灵活多变的需求。当业务系统的需求发生变化后,如果需要改动数据表的结构,则工作量相对较大,可以使用视图来减少改动的工作量。这种方式在实际工作中使用得比较多。
- **能够分解复杂的查询逻辑。**数据库中如果存在复杂的查询逻辑,则可以将问题进行分解,创建多个视图获取数据,再将创建的多个视图结合起来,完成复杂的查询逻辑。
7.2 缺点
如果我们在实际数据表的基础上创建了视图,那么, 如果实际数据表的结构变更了,我们就需要及时对相关的视图进行相应的维护 。特别是嵌套的视图(就是在视图的基础上创建视图),维护会变得比较复杂,可读性不好,容易变成系统的潜在隐患。因为创建视图的 SQL 查询可能会对字段重命名,也可能包含复杂的逻辑,这些都会增加维护的成本。
实际项目中,如果视图过多,会导致数据库维护成本的问题。所以,在创建视图的时候,你要结合实际项目需求,综合考虑视图的优点和不足,这样才能正确使用视图,使系统整体达到最优。
八、存储过程与函数
1、存储过程概述
1.1 介绍
含义 :存储过程的英文是 Stored Procedure。它的思想很简单,就是一组经过预先编译的 SQL 语句的封装。执行过程:存储过程预先存储在 MySQL 服务器上,需要执行的时候,客户端只需要向服务器端发出调用存储过程的命令,服务器端就可以把预先存储好的这一系列 SQL 语句全部执行。
好处 :
- 简化操作,提高了sql语句的重用性,减少了开发程序员的压力
- 减少操作过程中的失误,提高效率
- 减少网络传输量(客户端不需要把所有的 SQL 语句通过网络发给服务器)
- 减少了 SQL 语句暴露在网上的风险,也提高了数据查询的安全性
和视图、函数的对比 :
它和视图有着同样的优点,清晰、安全,还可以减少网络传输量。不过它和视图不同,视图是虚拟表,通常不对底层数据表直接操作,而存储过程是程序化的 SQL,可以直接操作底层数据表,相比于面向集合的操作方式,能够实现一些更复杂的数据处理。一旦存储过程被创建出来,使用它就像使用函数一样简单,我们直接通过调用存储过程名即可。相较于函数,存储过程是没有返回值的。
1.2 分类
存储过程的参数类型可以是IN、OUT和INOUT。根据这点分类如下:
- 没有参数(无参数无返回)
- 仅仅带 IN 类型(有参数无返回)
- 仅仅带 OUT 类型(无参数有返回)
- 既带 IN 又带 OUT(有参数有返回)
- 带 INOUT(有参数有返回)
注意:IN、OUT、INOUT 都可以在一个存储过程中带多个。
2、创建存储过程
2.1 语法分析
CREATE PROCEDURE 存储过程名(IN|OUT|INOUT 参数名 参数类型,...)
[characteristics ...]
BEGIN
存储过程体
END
说明:
- 参数前面的符号的意思
- IN:当前参数为输入参数,也就是表示入参;存储过程只是读取这个参数的值。如果没有定义参数种类,默认就是 IN,表示输入参数。
- OUT:当前参数为输出参数,也就是表示出参;执行完成之后,调用这个存储过程的客户端或者应用程序就可以读取这个参数返回的值了。
- INOUT:当前参数既可以为输入参数,也可以为输出参数。
- 形参类型可以是 MySQL数据库中的任意类型。
- characteristics 表示创建存储过程时指定的对存储过程的约束条件
LANGUAGE SQL
| [NOT] DETERMINISTIC
| { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }
| SQL SECURITY { DEFINER | INVOKER }
| COMMENT 'string'
- 存储过程体中可以有多条 SQL 语句,如果仅仅一条SQL 语句,则可以省略 BEGIN 和 END
编写存储过程并不是一件简单的事情,可能存储过程中需要复杂的 SQL 语句。 - 需要设置新的结束标记,因为MySQL默认的语句结束符号为分号‘;’。为了避免与存储过程中SQL语句结束符相冲突,需要使用DELIMITER改变存储过程的结束符。
2.2 举例
-- 举例1
DELIMITER $
CREATE PROCEDURE select_all_data()
BEGIN
SELECT * FROM emps;
END $
DELIMITER ;
-- 举例2,创建存储过程show_max_salary(),用来查看“emps”表的最高薪资值
DELIMITER //
CREATE PROCEDURE show_max_salary()
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT '查看最高薪资'
BEGIN
SELECT MAX(salary) FROM emps;
END //
DELIMITER ;
-- 查看“emps”表的最低薪资值。并将最低薪资通过OUT参数“ms”输出
DELIMITER //
CREATE PROCEDURE show_min_salary(OUT ms DOUBLE)
BEGIN
SELECT MIN(salary) INTO ms FROM emps;
END //
DELIMITER ;
-- 查看“emps”表的某个员工的薪资,并用IN参数empname输入员工姓名
DELIMITER //
CREATE PROCEDURE show_someone_salary(IN empname VARCHAR( 20 ))
BEGIN
SELECT salary FROM emps WHERE ename = empname;
END //
DELIMITER ;
3、调用存储过程
-- 存储过程有多种调用方法。存储过程必须使用CALL语句调用,并且存储过程和数据库相关,如果要执行其他数据库中的存储过程,需要指定数据库名称
CALL 存储过程名(实参列表)
-- 调用in模式
CALL sp1('值');
-- 调用out模式的参数
SET @name;
CALL sp1(@name);
SELECT @name;
-- 调用inout模式的参数
SET @name=值;
CALL sp1(@name);
SELECT @name;
4、存储函数的使用
4.1 语法分析
CREATE FUNCTION 函数名(参数名 参数类型,...)
RETURNS 返回值类型
[characteristics ...]
BEGIN
函数体 #函数体中肯定有 RETURN 语句
END
- 参数列表:指定参数为IN、OUT或INOUT只对PROCEDURE是合法的,FUNCTION中总是默认为IN参数。
- RETURNS type 语句表示函数返回数据的类型;
RETURNS子句只能对FUNCTION做指定,对函数而言这是强制的。它用来指定函数的返回类型,而且函数体必须包含一个RETURN value语句。 - characteristic 创建函数时指定的对函数的约束。取值与创建存储过程时相同,这里不再赘述。
- 函数体也可以用BEGIN…END来表示SQL代码的开始和结束。如果函数体只有一条语句,也可以省略BEGIN…END。
4.2 调用存储函数
在MySQL中,存储函数的使用方法与MySQL内部函数的使用方法是一样的。换言之,用户自己定义的存储函数与MySQL内部函数是一个性质的。区别在于,存储函数是用户自己定义的,而内部函数是MySQL的开发者定义的
SELECT 函数名(实参列表)
-- 举例
DELIMITER //
CREATE FUNCTION email_by_id(emp_id INT)
RETURNS VARCHAR( 25 )
DETERMINISTIC
CONTAINS SQL
BEGIN
RETURN (SELECT email FROM employees WHERE employee_id = emp_id);
END //
DELIMITER ;
-- 调用
SET @emp_id = 102 ;
SELECT email_by_id( 102 );
注意:
若在创建存储函数中报错“you might want to use the less safe log_bin_trust_function_creators variable”,有两种处理方法:
- 方式 1 :加上必要的函数特性“[NOT] DETERMINISTIC”和“{CONTAINS SQL | NO SQL | READS SQL DATA |MODIFIES SQL DATA}”
- 方式 2 :
SET GLOBAL log_bin_trust_function_creators = 1 ;
4.3 对比存储函数和存储过程
**关键字 | 调用语法 | 返回值 | 应用场景 | |
---|---|---|---|---|
存储过程 | PROCEDURE | CALL 存储过程() | 理解为有 0 个或多个 | 一般用于更新 |
存储函数 | FUNCTION | SELECT 函数() | 只能是一个 | 一般用于查询结果为一个值并返回时 |
5、存储过程和函数的查看、修改、删除
-- 查看存储过程和函数的创建信息
SHOW CREATE {PROCEDURE | FUNCTION} 存储过程名或函数名
-- 举例
SHOW CREATE FUNCTION test_db.CountProc \G
-- 使用SHOW STATUS语句查看存储过程和函数的状态信息
SHOW {PROCEDURE | FUNCTION} STATUS [LIKE 'pattern']
-- 举例
SHOW PROCEDURE STATUS LIKE 'SELECT%' \G
-- 从information_schema.Routines表中查看存储过程和函数的信息
SELECT * FROM information_schema.Routines
WHERE ROUTINE_NAME='存储过程或函数的名'[AND ROUTINE_TYPE={'PROCEDURE|FUNCTION'}];
-- 举例:从Routines表中查询名称为CountProc的存储函数的信息
SELECT * FROM information_schema.Routines
WHERE ROUTINE_NAME='count_by_id' AND ROUTINE_TYPE = 'FUNCTION' \G
-- 修改存储过程或函数,不影响存储过程或函数功能,只是修改相关特性
ALTER {PROCEDURE | FUNCTION} 存储过程或函数的名 [characteristic ...]
-- 其中,characteristic指定存储过程或函数的特性,其取值信息与创建存储过程、函数时的取值信息略有不同
{ CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }
| SQL SECURITY { DEFINER | INVOKER }
| COMMENT 'string'
-- 举例
ALTER FUNCTION CountProc
READS SQL DATA
COMMENT 'FIND NAME' ;
-- 删除
DROP {PROCEDURE | FUNCTION} [IF EXISTS] 存储过程或函数的名
DROP PROCEDURE CountProc;
DROP FUNCTION CountProc;
6、总结
6.1 优点
- **存储过程可以一次编译多次使用。**存储过程只在创建时进行编译,之后的使用都不需要重新编译,这就提升了 SQL 的执行效率。
- 可以减少开发工作量。 将代码封装成模块,实际上是编程的核心思想之一,这样可以把复杂的问题拆解成不同的模块,然后模块之间可以重复使用,在减少开发工作量的同时,还能保证代码的结构清晰。
- 存储过程的安全性强。 我们在设定存储过程的时候可以设置对用户的使用权限,这样就和视图一样具有较强的安全性。
- 可以减少网络传输量。 因为代码封装到存储过程中,每次使用只需要调用存储过程即可,这样就减少了网络传输量。
- 良好的封装性。 在进行相对复杂的数据库操作时,原本需要使用一条一条的 SQL 语句,可能要连接多次数据库才能完成的操作,现在变成了一次存储过程,只需要连接一次即可。
6.2 缺点
阿里开发规范
【强制】禁止使用存储过程,存储过程难以调试和扩展,更没有移植性。
- 可移植性差。 存储过程不能跨数据库移植,比如在 MySQL、Oracle 和 SQL Server 里编写的存储过程,在换成其他数据库时都需要重新编写。
- 调试困难。 只有少数 DBMS 支持存储过程的调试。对于复杂的存储过程来说,开发和维护都不容易。虽然也有一些第三方工具可以对存储过程进行调试,但要收费。
- 存储过程的版本管理很困难。 比如数据表索引发生变化了,可能会导致存储过程失效。我们在开发软件的时候往往需要进行版本管理,但是存储过程本身没有版本控制,版本迭代更新的时候很麻烦。
- 它不适合高并发的场景。 高并发的场景需要减少数据库的压力,有时数据库会采用分库分表的方式,而且对可扩展性要求很高,在这种情况下,存储过程会变得难以维护,增加数据库的压力,显然就不适用了。
九、变量、流程控制、游标与触发器
1、变量
1.1 系统变量
MySQL文档的系统变量:https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html
变量由系统定义,不是用户定义,属于服务器层面。启动MySQL服务,生成MySQL服务实例期间,MySQL将为MySQL服务器内存中的系统变量赋值,这些系统变量定义了当前MySQL服务实例的属性、特征。这些系统变量的值要么是编译MySQL时参数的默认值,要么是配置文件(例如my.ini等)中的参数值。
系统变量分为全局系统变量(需要添加global 关键字)以及会话系统变量(需要添加 session 关键字),有时也把全局系统变量简称为全局变量,有时也把会话系统变量称为local变量。 如果不写,默认会话级别。 静态变量(在 MySQL 服务实例运行期间它们的值不能使用 set 动态修改)属于特殊的全局系统变量。
每一个MySQL客户机成功连接MySQL服务器后,都会产生与之对应的会话。会话期间,MySQL服务实例会在MySQL服务器内存中生成与该会话对应的会话系统变量,这些会话系统变量的初始值是全局系统变量值的复制。如下图
- 全局系统变量针对于所有会话(连接)有效,但不能跨重启
- 会话系统变量仅针对于当前会话(连接)有效。会话期间,当前会话对某个会话系统变量值的修改,不会影响其他会话同一个会话系统变量的值。
- 会话 1 对某个全局系统变量值的修改会导致会话 2 中同一个全局系统变量值的修改。
在MySQL中有些系统变量只能是全局的,例如 max_connections 用于限制服务器的最大连接数;有些系统变量作用域既可以是全局又可以是会话,例如 character_set_client 用于设置客户端的字符集;有些系统变量的作用域只能是当前会话,例如 pseudo_thread_id 用于标记当前会话的 MySQL 连接 ID。
-- 查看系统变量
-- 查看所有全局变量
SHOW GLOBAL VARIABLES;
-- 查看所有会话变量
SHOW SESSION VARIABLES;
SHOW VARIABLES;
-- 查看满足条件的部分系统变量。
SHOW GLOBAL VARIABLES LIKE '%标识符%';
-- 查看满足条件的部分会话变量
SHOW SESSION VARIABLES LIKE '%标识符%';
-- 查看指定系统变量
-- 作为 MySQL 编码规范,MySQL 中的系统变量以两个“@”开头,其中“@@global”仅用于标记全局系统变量,“@@session”仅用于标记会话系统变量。
-- “@@”首先标记会话系统变量,如果会话系统变量不存在,则标记全局系统变量
-- 查看指定的系统变量的值
SELECT @@global.变量名;
-- 查看指定的会话变量的值
SELECT @@session.变量名;
SELECT @@变量名;
-- 修改系统变量的值
-- 方式一修改MySQL配置文件,继而修改MySQL系统变量的值(该方法需要重启MySQL服务)
-- 方式二在MySQL服务运行期间,使用“set”命令重新设置系统变量的值
-- 为某个系统变量赋值
SET @@global.变量名=变量值;
SET GLOBAL 变量名=变量值;
-- 为某个会话变量赋值
SET @@session.变量名=变量值;
SET SESSION 变量名=变量值;
1.2 用户变量
用户变量是用户自己定义的,作为 MySQL 编码规范,MySQL 中的用户变量以一个“@”开头。根据作用范围不同,又分为会话用户变量和局部变量。
- 会话用户变量:作用域和会话变量一样,只对当前连接会话有效。
- 局部变量:只在 BEGIN 和 END 语句块中有效。局部变量只能在存储过程和函数中使用。
-- 会话用户变量
-- 变量定义
-- 方式 1 :“=”或“:=”
SET @用户变量 = 值;
SET @用户变量 := 值;
-- 方式 2 :“:=” 或 INTO关键字
SELECT @用户变量 := 表达式 [FROM 等子句];
SELECT 表达式 INTO @用户变量 [FROM 等子句];
-- 查询
SELECT @用户变量
-- 局部变量
-- 如果没有DEFAULT子句,初始值为NULL
DECLARE 变量名 类型 [default 值];
-- 赋值,一般用于赋简单的值
SET 变量名=值;
SET 变量名:=值;
-- 一般用于赋表中的字段值
SELECT 字段名或表达式 INTO 变量名 FROM 表;
-- 使用变量
SELECT 局部变量名;
1.3 MySQL 8. 0 的新特性—全局变量的持久化
使用SET GLOBAL语句设置的变量值只会临时生效。数据库重启后,服务器又会从MySQL配置文件中读取变量的默认值。 MySQL 8.0版本新增了SET PERSIST命令。例如,设置服务器的最大连接数为 1000 :
SET PERSIST global max_connections = 1000 ;
show variables like '%max_connections%';
MySQL会将该命令的配置保存到数据目录下的mysqld-auto.cnf文件中,下次启动时会读取该文件,用其中的配置来覆盖默认的配置文件。
2、定义条件与处理程序
定义条件是事先定义程序执行过程中可能遇到的问题,处理程序定义了在遇到问题时应当采取的处理方式,并且保证存储过程或函数在遇到警告或错误时能继续执行。这样可以增强存储程序处理问题的能力,避免程序异常停止运行。说明:定义条件和处理程序在存储过程、存储函数中都是支持的。
2.1 定义条件
定义条件就是给MySQL中的错误码命名,这有助于存储的程序代码更清晰。它将一个错误名字和指定的错误条件关联起来。这个名字可以随后被用在定义处理程序的DECLARE HANDLER语句中。定义条件使用DECLARE语句,语法格式如下:
DECLARE 错误名称 CONDITION FOR 错误码(或错误条件)
- MySQL_error_code和sqlstate_value都可以表示MySQL的错误。
- MySQL_error_code是数值类型错误代码。
- sqlstate_value是长度为 5 的字符串类型错误代码。
- 例如,在ERROR 1418 (HY000)中,1418 是MySQL_error_code,’HY000’是sqlstate_value。
- 例如,在ERROR 1142 (42000)中,1142 是MySQL_error_code,’42000’是sqlstate_value。
-- 举例 定义”ERROR 1148(42000)”错误,名称为command_not_allowed
-- 使用MySQL_error_code
DECLARE command_not_allowed CONDITION FOR 1148 ;
-- 使用sqlstate_value
DECLARE command_not_allowed CONDITION FOR SQLSTATE '42000';
2.2 定义处理程序
可以为SQL执行过程中发生的某种类型的错误定义特殊的处理程序。定义处理程序时,使用DECLARE语句的语法如下:
DECLARE 处理方式 HANDLER FOR 错误类型 处理语句
- 处理方式 :处理方式有 3 个取值:CONTINUE、EXIT、UNDO。
- CONTINUE:表示遇到错误不处理,继续执行。
- EXIT:表示遇到错误马上退出。
- UNDO:表示遇到错误后撤回之前的操作。MySQL中暂时不支持这样的操作。
- 错误类型 (即条件)可以有如下取值:
- SQLSTATE ‘字符串错误码’:表示长度为 5 的sqlstate_value类型的错误代码;
- MySQL_error_code:匹配数值类型错误代码;
- 错误名称:表示DECLARE … CONDITION定义的错误条件名称。
- SQLWARNING:匹配所有以 01 开头的SQLSTATE错误代码;
- NOT FOUND:匹配所有以 02 开头的SQLSTATE错误代码;
- SQLEXCEPTION:匹配所有没有被SQLWARNING或NOT FOUND捕获的SQLSTATE错误代码;
- 处理语句 :如果出现上述条件之一,则采用对应的处理方式,并执行指定的处理语句。语句可以是像“SET 变量 = 值”这样的简单语句,也可以是使用BEGIN … END编写的复合语句。
定义处理程序的几种方式,代码如下:
-- 方法 1 :捕获sqlstate_value
DECLARE CONTINUE HANDLER FOR SQLSTATE '42S02' SET @info='NO_SUCH_TABLE';
-- 方法 2 :捕获mysql_error_value
DECLARE CONTINUE HANDLER FOR 1146 SET @info = 'NO_SUCH_TABLE';
-- 方法 3 :先定义条件,再调用
DECLARE no_such_table CONDITION FOR 1146 ;
DECLARE CONTINUE HANDLER FOR NO_SUCH_TABLE SET @info = 'NO_SUCH_TABLE';
-- 方法 4 :使用SQLWARNING
DECLARE EXIT HANDLER FOR SQLWARNING SET @info = 'ERROR';
-- 方法 5 :使用NOT FOUND
DECLARE EXIT HANDLER FOR NOT FOUND SET @info = 'NO_SUCH_TABLE';
-- 方法 6 :使用SQLEXCEPTION
DECLARE EXIT HANDLER FOR SQLEXCEPTION SET @info = 'ERROR';
2.3 案例解决
在存储过程中,定义处理程序,捕获sqlstate_value值,当遇到MySQL_error_code值为 1048 时,执行CONTINUE操作,并且将@proc_value的值设置为-1
DELIMITER //
CREATE PROCEDURE InsertDataWithCondition()
BEGIN
DECLARE duplicate_entry CONDITION FOR SQLSTATE '23000' ;
DECLARE EXIT HANDLER FOR duplicate_entry SET @proc_value = - 1 ;
SET @x = 1 ;
INSERT INTO departments(department_name) VALUES('测试');
SET @x = 2 ;
INSERT INTO departments(department_name) VALUES('测试');
SET @x = 3 ;
END //
DELIMITER ;
3、流程控制
解决复杂问题不可能通过一个 SQL 语句完成,我们需要执行多个 SQL 操作。流程控制语句的作用就是控制存储过程中 SQL 语句的执行顺序,是我们完成复杂操作必不可少的一部分。只要是执行的程序,流程就分为三大类:
- 顺序结构:程序从上往下依次执行
- 分支结构:程序按条件进行选择执行,从两条或多条路径中选择一条执行
- 循环结构:程序满足一定条件下,重复执行一组语句
针对于MySQL 的流程控制语句主要有 3 类。注意:只能用于存储程序。
- 条件判断语句:IF 语句和 CASE 语句
- 循环语句:LOOP、WHILE 和 REPEAT 语句
- 跳转语句:ITERATE 和 LEAVE 语句
-- 分支结构之 IF
IF 表达式 1 THEN 操作 1
[ELSEIF 表达式 2 THEN 操作2]......
[ELSE 操作N]
END IF
-- 分支结构之 CASE
CASE
WHEN 条件 1 THEN 结果 1 或语句1(如果是语句,需要加分号)
WHEN 条件 2 THEN 结果 2 或语句2(如果是语句,需要加分号)
...
ELSE 结果n或语句n(如果是语句,需要加分号)
END [case](如果是放在begin end中需要加上case,如果放在select后面不需要)
-- 循环结构之LOOP
[loop_label:] LOOP
循环执行的语句
END LOOP [loop_label]
-- 循环结构之WHILE
[while_label:] WHILE 循环条件 DO
循环体
END WHILE [while_label];
-- 循环结构之REPEAT
[repeat_label:] REPEAT
循环体的语句
UNTIL 结束循环的条件表达式
END REPEAT [repeat_label]
-- 跳转语句之LEAVE语句,可以把 LEAVE 理解为 break
LEAVE 标记名
-- 跳转语句之ITERATE语句,可以把 ITERATE 理解为 continue
ITERATE label
4、游标
4.1 什么是游标(或光标)
虽然我们也可以通过筛选条件 WHERE 和 HAVING,或者是限定返回记录的关键字 LIMIT 返回一条记录,但是,却无法在结果集中像指针一样,向前定位一条记录、向后定位一条记录,或者是随意定位到某一条记录,并对记录的数据进行处理。这个时候,就可以用到游标。游标,提供了一种灵活的操作方式,让我们能够对结果集中的每一条记录进行定位,并对指向的记录中的数据进行操作的数据结构。 游标让 SQL 这种面向集合的语言有了面向过程开发的能力。
在 SQL 中,游标是一种临时的数据库对象,可以指向存储在数据库表中的数据行指针。这里游标充当了指针的作用,我们可以通过操作游标来对数据行进行操作。MySQL中游标可以在存储过程和函数中使用。
4.2 游标的使用
-- 1、声明游标
DECLARE cursor_name CURSOR FOR select_statement;
-- 这个语法适用于 MySQL,SQL Server,DB2 和 MariaDB。如果是用 Oracle 或者 PostgreSQL,需要写成
DECLARE cursor_name CURSOR IS select_statement;
-- 要使用 SELECT 语句来获取数据结果集,而此时还没有开始遍历数据,这里 select_statement 代表的是SELECT 语句,返回一个用于创建游标的结果集。
-- 举例
DECLARE cur_emp CURSOR FOR
SELECT employee_id,salary FROM employees;
-- 2、打开游标的语法
OPEN cursor_name
-- 当我们定义好游标之后,如果想要使用游标,必须先打开游标。
-- 打开游标的时候 SELECT 语句的查询结果集就会送到游标工作区,为后面游标的逐条读取结果集中的记录做准备
-- 3、使用游标(从游标中取得数据)
FETCH cursor_name INTO var_name [, var_name] ...
-- 使用 cursor_name 这个游标来读取当前行,并且将数据保存到 var_name 这个变量中,游标指针指到下一行
-- 如果游标读取的数据行有多个列名,则在 INTO 关键字后面赋值给多个变量名即可
-- 游标的查询结果集中的字段数,必须跟 INTO 后面的变量数一致 ,否则,在存储过程执行的时候,MySQL 会提示错误
-- 4、关闭游标
CLOSE cursor_name
-- 如果不及时关闭, 游标会一直保持到存储过程结束 ,影响系统运行的效率
-- 举例
DELIMITER //
CREATE PROCEDURE get_count_by_limit_total_salary(IN limit_total_salary DOUBLE,OUT total_count INT)
BEGIN
DECLARE sum_salary DOUBLE DEFAULT 0 ; -- 记录累加的总工资
DECLARE cursor_salary DOUBLE DEFAULT 0 ; -- 记录某一个工资值
DECLARE emp_count INT DEFAULT 0 ; -- 记录循环个数
-- 定义游标
DECLARE emp_cursor CURSOR FOR SELECT salary FROM employees ORDER BY salary DESC;
-- 打开游标
OPEN emp_cursor;
REPEAT
-- 使用游标(从游标中获取数据)
FETCH emp_cursor INTO cursor_salary;
SET sum_salary = sum_salary + cursor_salary;
SET emp_count = emp_count + 1 ;
UNTIL sum_salary >= limit_total_salary
END REPEAT;
SET total_count = emp_count;
-- 关闭游标
CLOSE emp_cursor;
END //
DELIMITER ;
4.3 小结
游标是 MySQL 的一个重要的功能,为逐条读取结果集中的数据,提供了完美的解决方案。跟在应用层面实现相同的功能相比,游标可以在存储程序中使用,效率高,程序也更加简洁。但同时也会带来一些性能问题,比如在使用游标的过程中,会对数据行进行加锁,这样在业务并发量大的时候,不仅会影响业务之间的效率,还会消耗系统资源,造成内存不足,这是因为游标是在内存中进行的处理。
建议:养成用完之后就关闭的习惯,这样才能提高系统的整体效率。
5、触发器
5.1 触发器概述
MySQL从5.0.2版本开始支持触发器。MySQL的触发器和存储过程一样,都是嵌入到MySQL服务器的一段程序。触发器是由事件来触发某个操作,这些事件包括INSERT、UPDATE、DELETE事件。所谓事件就是指用户的动作或者触发某项行为。如果定义了触发程序,当数据库执行这些语句时候,就相当于事件发生了,就会自动激发触发器执行相应的操作。当对数据表中的数据执行插入、更新和删除操作,需要自动执行一些数据库逻辑时,可以使用触发器来实现。
5.2 触发器应用场景
-- 创建触发器的语法结构
CREATE TRIGGER 触发器名称
{BEFORE|AFTER} {INSERT|UPDATE|DELETE} ON 表名
FOR EACH ROW
触发器执行的语句块;
-- 表名:表示触发器监控的对象。
-- BEFORE|AFTER:表示触发的时间。BEFORE 表示在事件之前触发;AFTER 表示在事件之后触发。
-- INSERT|UPDATE|DELETE:表示触发的事件。
-- INSERT 表示插入记录时触发;
-- UPDATE 表示更新记录时触发;
-- DELETE 表示删除记录时触发。
-- 触发器执行的语句块:可以是单条SQL语句,也可以是由BEGIN…END结构组成的复合语句块。
-- 举例1
-- 向test_trigger数据表插入数据之前,向test_trigger_log数据表中插入before_insert的日志信息
DELIMITER //
CREATE TRIGGER before_insert
BEFORE INSERT ON test_trigger
FOR EACH ROW
BEGIN
INSERT INTO test_trigger_log (t_log)
VALUES('before_insert');
END //
DELIMITER ;
-- 举例2
-- 在INSERT之前检查将要添加的新员工薪资是否大于他领导的薪资,如果大于领导薪资,则报sqlstate_value为’HY000’的错误,从而使得添加失败
DELIMITER //
CREATE TRIGGER salary_check_trigger
BEFORE INSERT ON employees FOR EACH ROW
BEGIN
DECLARE mgrsalary DOUBLE;
SELECT salary INTO mgrsalary FROM employees WHERE employee_id = NEW.manager_id;
IF NEW.salary > mgrsalary THEN
SIGNAL SQLSTATE 'HY000' SET MESSAGE_TEXT = '薪资高于领导薪资错误';
END IF;
END //
DELIMITER ;
-- 查看、删除触发器
-- 查看当前数据库的所有触发器的定义
SHOW TRIGGERS\G
-- 查看当前数据库中某个触发器的定义
SHOW CREATE TRIGGER 触发器名
-- 从系统库information_schema的TRIGGERS表中查询“salary_check_trigger”触发器的信息
SELECT * FROM information_schema.TRIGGERS;
-- 触发器也是数据库对象,删除触发器也用DROP语句
DROP TRIGGER IF EXISTS 触发器名称;
5.3 触发器的优缺点
优点:
- 触发器可以确保数据的完整性
- 触发器可以帮助我们记录操作日志
- 触发器还可以用在操作数据前,对数据进行合法性检查
缺点:
- 触发器最大的一个问题就是可读性差
- 相关数据的变更,可能会导致触发器出错
注意点:
注意,如果在子表中定义了外键约束,并且外键指定了ON UPDATE/DELETE CASCADE/SET NULL子句,此时修改父表被引用的键值或删除父表被引用的记录行时,也会引起子表的修改和删除操作,此时基于子表的UPDATE和DELETE语句定义的触发器并不会被激活。
十、MySQL8其他新特性
1、MySQL 8. 0 新增特性
1.1 MySQL 8. 0 新增特性
- 更简便的NoSQL支持 NoSQL泛指非关系型数据库和数据存储。随着互联网平台的规模飞速发展,传统的关系型数据库已经越来越不能满足需求。从5.6版本开始,MySQL就开始支持简单的NoSQL存储功能。MySQL 8对这一功能做了优化,以更灵活的方式实现NoSQL功能,不再依赖模式(schema)。
- 更好的索引 在查询中,正确地使用索引可以提高查询的效率。MySQL 8中新增了隐藏索引和降序索引。隐藏索引可以用来测试去掉索引对查询性能的影响。在查询中混合存在多列索引时,使用降序索引可以提高查询的性能。
- 更完善的JSON支持 MySQL从5.7开始支持原生JSON数据的存储,MySQL 8对这一功能做了优化,增加了聚合函数JSON_ARRAYAGG()和JSON_OBJECTAGG(),将参数聚合为JSON数组或对象,新增了行内操作符 ->>,是列路径运算符 ->的增强,对JSON排序做了提升,并优化了JSON的更新操作。
- 安全和账户管理 MySQL 8中新增了caching_sha2_password 授权插件、角色、密码历史记录和FIPS模式支持,这些特性提高了数据库的安全性和性能,使数据库管理员能够更灵活地进行账户管理工作。
- InnoDB的变化 InnoDB是MySQL默认的存储引擎,是事务型数据库的首选引擎,支持事务安全表(ACID),支持行锁定和外键。在MySQL 8 版本中,InnoDB在自增、索引、加密、死锁、共享锁等方面做了大量的改进和优化,并且支持原子数据定义语言(DDL),提高了数据安全性,对事务提供更好的支持。
- 数据字典 在之前的MySQL版本中,字典数据都存储在元数据文件和非事务表中。从MySQL 8开始新增了事务数据字典,在这个字典里存储着数据库对象信息,这些数据字典存储在内部事务表中。
- 原子数据定义语句 MySQL 8开始支持原子数据定义语句(Automic DDL),即原子DDL。目前,只有InnoDB存储引擎支持原子DDL。原子数据定义语句(DDL)将与DDL操作相关的数据字典更新、存储引擎操作、二进制日志写入结合到一个单独的原子事务中,这使得即使服务器崩溃,事务也会提交或回滚。使用支持原子操作的存储引擎所创建的表,在执行DROP TABLE、CREATE TABLE、ALTER TABLE、RENAME TABLE、TRUNCATE TABLE、CREATE TABLESPACE、DROP TABLESPACE等操作时,都支持原子操作,即事务要么完全操作成功,要么失败后回滚,不再进行部分提交。 对于从MySQL 5.7复制到MySQL 8版本中的语句,可以添加IF EXISTS或IF NOT EXISTS语句来避免发生错误。
- 资源管理 MySQL 8开始支持创建和管理资源组,允许将服务器内运行的线程分配给特定的分组,以便线程根据组内可用资源执行。组属性能够控制组内资源,启用或限制组内资源消耗。数据库管理员能够根据不同的工作负载适当地更改这些属性。 目前,CPU时间是可控资源,由“虚拟CPU”这个概念来表示,此术语包含CPU的核心数,超线程,硬件线程等等。服务器在启动时确定可用的虚拟CPU数量。拥有对应权限的数据库管理员可以将这些CPU与资源组关联,并为资源组分配线程。 资源组组件为MySQL中的资源组管理提供了SQL接口。资源组的属性用于定义资源组。MySQL中存在两个默认组,系统组和用户组,默认的组不能被删除,其属性也不能被更改。对于用户自定义的组,资源组创建时可初始化所有的属性,除去名字和类型,其他属性都可在创建之后进行更改。 在一些平台下,或进行了某些MySQL的配置时,资源管理的功能将受到限制,甚至不可用。例如,如果安装了线程池插件,或者使用的是macOS系统,资源管理将处于不可用状态。在FreeBSD和Solaris系统中,资源线程优先级将失效。在Linux系统中,只有配置了CAP_SYS_NICE属性,资源管理优先级才能发挥作用。
- 字符集支持 MySQL 8中默认的字符集由latin1更改为utf8mb4,并首次增加了日语所特定使用的集合,utf8mb4_ja_0900_as_cs。
- 优化器增强 MySQL优化器开始支持隐藏索引和降序索引。隐藏索引不会被优化器使用,验证索引的必要性时不需要删除索引,先将索引隐藏,如果优化器性能无影响就可以真正地删除索引。降序索引允许优化器对多个列进行排序,并且允许排序顺序不一致。
- 公用表表达式 公用表表达式(Common Table Expressions)简称为CTE,MySQL现在支持递归和非递归两种形式的CTE。CTE通过在SELECT语句或其他特定语句前使用WITH语句对临时结果集进行命名。基础语法如下:
WITH cte_name (col_name1,col_name2 ...) AS (Subquery) SELECT * FROM cte_name;
Subquery代表子查询,子查询前使用WITH语句将结果集命名为cte_name,在后续的查询中即可使用cte_name进行查询。 - 窗口函数 MySQL 8开始支持窗口函数。在之前的版本中已存在的大部分聚合函数在MySQL 8中也可以作为窗口函数来使用。
- 正则表达式支持 MySQL在8.0.4以后的版本中采用支持Unicode的国际化组件库实现正则表达式操作,这种方式不仅能提供完全的Unicode支持,而且是多字节安全编码。MySQL增加了REGEXP_LIKE()、EGEXP_INSTR()、REGEXP_REPLACE()和 REGEXP_SUBSTR()等函数来提升性能。另外,regexp_stack_limit和regexp_time_limit 系统变量能够通过匹配引擎来控制资源消耗。
- 内部临时表 TempTable存储引擎取代MEMORY存储引擎成为内部临时表的默认存储引擎。TempTable存储引擎为VARCHAR和VARBINARY列提供高效存储。internal_tmp_mem_storage_engine会话变量定义了内部临时表的存储引擎,可选的值有两个,TempTable和MEMORY,其中TempTable为默认的存储引擎。temptable_max_ram系统配置项定义了TempTable存储引擎可使用的最大内存数量。
- 日志记录 在MySQL 8中错误日志子系统由一系列MySQL组件构成。这些组件的构成由系统变量log_error_services来配置,能够实现日志事件的过滤和写入。
- 备份锁 新的备份锁允许在线备份期间执行数据操作语句,同时阻止可能造成快照不一致的操作。新备份锁由 LOCK INSTANCE FOR BACKUP 和 UNLOCK INSTANCE 语法提供支持,执行这些操作需要备份管理员特权。
- 增强的MySQL复制 MySQL 8复制支持对JSON文档进行部分更新的二进制日志记录,该记录使用紧凑的二进制格式,从而节省记录完整JSON文档的空间。当使用基于语句的日志记录时,这种紧凑的日志记录会自动完成,并且可以通过将新的binlog_row_value_options系统变量值设置为PARTIAL_JSON来启用。
1.2 MySQL 8. 0 移除的旧特性
在MySQL 5.7版本上开发的应用程序如果使用了MySQL8.0 移除的特性,语句可能会失败,或者产生不同的执行结果。为了避免这些问题,对于使用了移除特性的应用,应当尽力修正避免使用这些特性,并尽可能使用替代方法。
-
查询缓存 查询缓存已被移除,删除的项有:
(1) 语句: FLUSH QUERY CACHE和RESET QUERY CACHE。
(2) 系统变量: query_cache_limit、query_cache_min_res_unit、query_cache_size、query_cache_type、query_cache_wlock_invalidate。
(3) 状态变量: Qcache_free_blocks、Qcache_free_memory、Qcache_hits、Qcache_inserts、Qcache_lowmem_prunes、Qcache_not_cached、Qcache_queries_in_cache、Qcache_total_blocks。
(4) 线程状态: checking privileges on cached query、checking query cache for query、invalidating query cache entries、sending cached result to client、storing result in query cache、waiting for query cache lock。
-
加密相关 删除的加密相关的内容有:ENCODE()、DECODE()、ENCRYPT()、DES_ENCRYPT()和DES_DECRYPT()函数,配置项des-key-file,系统变量have_crypt,FLUSH语句的DES_KEY_FILE选项,HAVE_CRYPT CMake选项。 对于移除的ENCRYPT()函数,考虑使用SHA2()替代,对于其他移除的函数,使用AES_ENCRYPT()和AES_DECRYPT()替代。
-
空间函数相关 在MySQL 5.7版本中,多个空间函数已被标记为过时。这些过时函数在MySQL 8中都已被移除,只保留了对应的ST_和MBR函数。
-
\N和NULL 在SQL语句中,解析器不再将\N视为NULL,所以在SQL语句中应使用NULL代替\N。这项变化不会影响使用LOAD DATA INFILE或者SELECT…INTO OUTFILE操作文件的导入和导出。在这类操作中,NULL仍等同于\N。
-
mysql_install_db 在MySQL分布中,已移除了mysql_install_db程序,数据字典初始化需要调用带着–initialize或者–initialize-insecure选项的mysqld来代替实现。另外,–bootstrap和INSTALL_SCRIPTDIR CMake也已被删除。
-
通用分区处理程序 通用分区处理程序已从MySQL服务中被移除。为了实现给定表分区,表所使用的存储引擎需要自有的分区处理程序。 提供本地分区支持的MySQL存储引擎有两个,即InnoDB和NDB,而在MySQL 8中只支持InnoDB。
-
系统和状态变量信息 在INFORMATION_SCHEMA数据库中,对系统和状态变量信息不再进行维护。GLOBAL_VARIABLES、SESSION_VARIABLES、GLOBAL_STATUS、SESSION_STATUS表都已被删除。另外,系统变量show_compatibility_56也已被删除。被删除的状态变量有Slave_heartbeat_period、Slave_last_heartbeat,Slave_received_heartbeats、Slave_retried_transactions、Slave_running。以上被删除的内容都可使用性能模式中对应的内容进行替代。
-
mysql_plugin工具 mysql_plugin工具用来配置MySQL服务器插件,现已被删除,可使用–plugin-load或–plugin-load-add选项在服务器启动时加载插件或者在运行时使用INSTALL PLUGIN语句加载插件来替代该工具。
2、新特性 1 :窗口函数
2.1 使用窗口函数前后对比
-- 假设我现在有这样一个数据表,它显示了某购物网站在每个城市每个区的销售额
CREATE TABLE sales(
id INT PRIMARY KEY AUTO_INCREMENT,
city VARCHAR( 15 ),
county VARCHAR( 15 ),
sales_value DECIMAL
);
INSERT INTO sales(city,county,sales_value)
VALUES
('北京','海淀',10.00),
('北京','朝阳',20.00),
('上海','黄埔',30.00),
('上海','长宁',10.00);
-- 现在计算这个网站在每个城市的销售总额、在全国的销售总额、每个区的销售额占所在城市销售额中的比率,以及占总销售额中的比率
-- 传统方式实现
-- 第一步,计算总销售金额,并存入临时表 a:
CREATE TEMPORARY TABLE a -- 创建临时表
SELECT SUM(sales_value) AS sales_value -- 计算总计金额
FROM sales;
-- 第二步,计算每个城市的销售总额并存入临时表 b
CREATE TEMPORARY TABLE b -- 创建临时表
SELECT city,SUM(sales_value) AS sales_value -- 计算城市销售合计
FROM sales
GROUP BY city;
-- 第三步,计算各区的销售占所在城市的总计金额的比例,和占全部销售总计金额的比例
SELECT s.city AS 城市,s.county AS 区,s.sales_value AS 区销售额,
b.sales_value AS 市销售额,s.sales_value/b.sales_value AS 市比率,
a.sales_value AS 总销售额,s.sales_value/a.sales_value AS 总比率
FROM sales s
JOIN b ON (s.city=b.city) -- 连接市统计结果临时表
JOIN a -- 连接总计金额临时表
ORDER BY s.city,s.county;
-- 使用窗口函数
SELECT city AS 城市,county AS 区,sales_value AS 区销售额,
SUM(sales_value) OVER(PARTITION BY city) AS 市销售额, -- 计算市销售额
sales_value/SUM(sales_value) OVER(PARTITION BY city) AS 市比率,
SUM(sales_value) OVER() AS 总销售额, -- 计算总销售额
sales_value/SUM(sales_value) OVER() AS 总比率
FROM sales
ORDER BY city,county;
-- 在这种需要用到分组统计的结果对每一条记录进行计算的场景下,使用窗口函数更好
2.2 窗口函数分类
官方文档:https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html#function_row-number
MySQL从 8. 0 版本开始支持窗口函数。窗口函数的作用类似于在查询中对数据进行分组,不同的是,分组操作会把分组的结果聚合成一条记录,而窗口函数是将结果置于每一条数据记录中。窗口函数可以分为静态窗口函数和动态窗口函数。
- 静态窗口函数的窗口大小是固定的,不会因为记录的不同而不同;
- 动态窗口函数的窗口大小会随着记录的不同而变化。
窗口函数总体上可以分为序号函数、分布函数、前后函数、首尾函数和其他函数,如下表:
2.3 语法格式
- OVER 关键字指定函数窗口的范围。
- 如果省略后面括号中的内容,则窗口会包含满足WHERE条件的所有记录,窗口函数会基于所有满足WHERE条件的记录进行计算。
- 如果OVER关键字后面的括号不为空,则可以使用如下语法设置窗口。
- 窗口名:为窗口设置一个别名,用来标识窗口。
- PARTITION BY子句:指定窗口函数按照哪些字段进行分组。分组后,窗口函数可以在每个分组中分别执行。
- ORDER BY子句:指定窗口函数按照哪些字段进行排序。执行排序操作使窗口函数按照排序后的数据记录的顺序进行编号。
- FRAME子句:为分区中的某个子集定义规则,可以用来作为滑动窗口使用
-- 窗口函数的语法结构
函数 OVER([PARTITION BY 字段名 ORDER BY 字段名 ASC|DESC])
-- 或者
函数 OVER 窗口名 ... WINDOW 窗口名 AS ([PARTITION BY 字段名 ORDER BY 字段名 ASC|DESC])
2.4 举例讲解
首先创建表和插入数据
CREATE TABLE goods(
id INT PRIMARY KEY AUTO_INCREMENT,
category_id INT,
category VARCHAR( 15 ),
NAME VARCHAR( 30 ),
price DECIMAL( 10 , 2 ),
stock INT,
upper_time DATETIME
);
INSERT INTO goods(category_id,category,NAME,price,stock,upper_time)
VALUES
( 1 , '女装/女士精品', 'T恤', 39.90, 1000 , '2020-11-10 00:00:00'),
( 1 , '女装/女士精品', '连衣裙', 79.90, 2500 , '2020-11-10 00:00:00'),
( 1 , '女装/女士精品', '卫衣', 89.90, 1500 , '2020-11-10 00:00:00'),
( 1 , '女装/女士精品', '牛仔裤', 89.90, 3500 , '2020-11-10 00:00:00'),
( 1 , '女装/女士精品', '百褶裙', 29.90, 500 , '2020-11-10 00:00:00'),
( 1 , '女装/女士精品', '呢绒外套', 399.90, 1200 , '2020-11-10 00:00:00'),
( 2 , '户外运动', '自行车', 399.90, 1000 , '2020-11-10 00:00:00'),
( 2 , '户外运动', '山地自行车', 1399.90, 2500 , '2020-11-10 00:00:00'),
( 2 , '户外运动', '登山杖', 59.90, 1500 , '2020-11-10 00:00:00'),
( 2 , '户外运动', '骑行装备', 399.90, 3500 , '2020-11-10 00:00:00'),
( 2 , '户外运动', '运动外套', 799.90, 500 , '2020-11-10 00:00:00'),
( 2 , '户外运动', '滑板', 499.90, 1200 , '2020-11-10 00:00:00');
不同函数讲解
-- ====================序号函数==========================
-- ROW_NUMBER()函数
-- ROW_NUMBER()函数能够对数据中的序号进行顺序显示
-- 举例:查询 goods 数据表中每个商品分类下价格降序排列的各个商品信息
SELECT ROW_NUMBER() OVER(PARTITION BY category_id ORDER BY price DESC) AS row_num,
id, category_id, category, NAME, price, stock
FROM goods;
-- 举例:查询 goods 数据表中每个商品分类下价格最高的 3 种商品信息
SELECT *
FROM (
SELECT ROW_NUMBER() OVER(PARTITION BY category_id ORDER BY price DESC) AS row_num,
id, category_id, category, NAME, price, stock
FROM goods) t
WHERE row_num <= 3 ;
-- RANK()函数
-- 使用RANK()函数能够对序号进行并列排序,并且会跳过重复的序号,比如序号为 1 、 1 、 3 。
-- 举例:使用RANK()函数获取 goods 数据表中各类别的价格从高到低排序的各商品信息。
SELECT RANK() OVER(PARTITION BY category_id ORDER BY price DESC) AS row_num,
id, category_id, category, NAME, price, stock
FROM goods;
-- 举例:使用RANK()函数获取 goods 数据表中类别为“女装/女士精品”的价格最高的 4 款商品信息
SELECT *
FROM(
SELECT RANK() OVER(PARTITION BY category_id ORDER BY price DESC) AS row_num,
id, category_id, category, NAME, price, stock
FROM goods) t
WHERE category_id = 1 AND row_num <= 4 ;
-- DENSE_RANK()函数
-- DENSE_RANK()函数对序号进行并列排序,并且不会跳过重复的序号,比如序号为1 、1 、2。
-- 举例:使用DENSE_RANK()函数获取 goods 数据表中各类别的价格从高到低排序的各商品信息。
SELECT DENSE_RANK() OVER(PARTITION BY category_id ORDER BY price DESC) AS row_num,
id, category_id, category, NAME, price, stock
FROM goods;
-- ============================分布函数===========================
-- PERCENT_RANK()函数
-- PERCENT_RANK()函数是等级值百分比函数。按照如下方式进行计算。其中,rank的值为使用RANK()函数产生的序号,rows的值为当前窗口的总记录数。
-- (rank - 1 ) / (rows - 1 )
-- 举例:计算 goods 数据表中名称为“女装/女士精品”的类别下的商品的PERCENT_RANK值。
-- 写法一:
SELECT RANK() OVER (PARTITION BY category_id ORDER BY price DESC) AS r,
PERCENT_RANK() OVER (PARTITION BY category_id ORDER BY price DESC) AS pr,
id, category_id, category, NAME, price, stock
FROM goods
WHERE category_id = 1 ;
-- 写法二:
SELECT RANK() OVER w AS r,
PERCENT_RANK() OVER w AS pr,
id, category_id, category, NAME, price, stock
FROM goods
WHERE category_id = 1 WINDOW w AS (PARTITION BY category_id ORDER BY price DESC);
-- CUME_DIST()函数
-- CUME_DIST()函数主要用于查询小于或等于某个值的比例
-- 举例:查询goods数据表中小于或等于当前价格的比例
SELECT CUME_DIST() OVER(PARTITION BY category_id ORDER BY price ASC) AS cd,
id, category, NAME, price
FROM goods;
-- ============================前后函数======================================
-- LAG(expr,n)函数
-- LAG(expr,n)函数返回当前行的前n行的expr的值。
-- 举例:查询goods数据表中前一个商品价格与当前商品价格的差值
SELECT id, category, NAME, price, pre_price, price - pre_price AS diff_price
FROM (
SELECT id, category, NAME, price,LAG(price, 1) OVER w AS pre_price
FROM goods
WINDOW w AS (PARTITION BY category_id ORDER BY price)) t;
-- LEAD(expr,n)函数
-- LEAD(expr,n)函数返回当前行的后n行的expr的值
-- 举例:查询goods数据表中后一个商品价格与当前商品价格的差值
SELECT id, category, NAME, behind_price, price,behind_price - price AS diff_price
FROM(
SELECT id, category, NAME, price,LEAD(price, 1 ) OVER w AS behind_price
FROM goods WINDOW w AS (PARTITION BY category_id ORDER BY price)) t;
-- ==================================首尾函数==================================
-- FIRST_VALUE(expr)函数
-- FIRST_VALUE(expr)函数返回第一个expr的值
-- 举例:按照价格排序,查询第 1 个商品的价格信息
SELECT id, category, NAME, price, stock,FIRST_VALUE(price) OVER w AS first_price
FROM goods WINDOW w AS (PARTITION BY category_id ORDER BY price);
-- LAST_VALUE(expr)函数
-- LAST_VALUE(expr)函数返回最后一个expr的值
-- 举例:按照价格排序,查询最后一个商品的价格信息
SELECT id, category, NAME, price, stock,LAST_VALUE(price) OVER w AS last_price
FROM goods WINDOW w AS (PARTITION BY category_id ORDER BY price);
--=================================其他函数========================
-- NTH_VALUE(expr,n)函数
-- NTH_VALUE(expr,n)函数返回第n个expr的值
-- 举例:查询goods数据表中排名第 2 和第 3 的价格信息
SELECT id, category, NAME, price,NTH_VALUE(price, 2 ) OVER w AS second_price,
NTH_VALUE(price, 3 ) OVER w AS third_price
FROM goods WINDOW w AS (PARTITION BY category_id ORDER BY price);
-- NTILE(n)函数
-- NTILE(n)函数将分区中的有序数据分为n个桶,记录桶编号
-- 举例:将goods表中的商品按照价格分为 3 组
SELECT NTILE( 3 ) OVER w AS nt,id, category, NAME, price
FROM goods WINDOW w AS (PARTITION BY category_id ORDER BY price);
窗口函数的特点是可以分组,而且可以在分组内排序。另外,窗口函数不会因为分组而减少原表中的行数,这对我们在原表数据的基础上进行统计和排序非常有用
3、新特性 2 :公用表表达式
公用表表达式(或通用表表达式)简称为CTE(Common Table Expressions)。CTE是一个命名的临时结果集,作用范围是当前语句。CTE可以理解成一个可以复用的子查询,当然跟子查询还是有点区别的,CTE可以引用其他CTE,但子查询不能引用其他子查询。所以,可以考虑代替子查询。依据语法结构和执行方式的不同,公用表表达式分为普通公用表表达式和递归公用表表达式 2 种
3.1 普通公用表表达式
-- 普通公用表表达式的语法结构
WITH CTE名称
AS (子查询)
SELECT|DELETE|UPDATE 语句;
-- 查询员工所在的部门的详细信息
SELECT * FROM departments
WHERE department_id IN (
SELECT DISTINCT department_id
FROM employees);
-- 这个查询也可以用普通公用表表达式的方式完成:
WITH emp_dept_id
AS (SELECT DISTINCT department_id FROM employees)
SELECT * FROM departments d JOIN emp_dept_id e
ON d.department_id = e.department_id;
3.2 递归公用表表达式
递归公用表表达式也是一种公用表表达式,只不过,除了普通公用表表达式的特点以外,它还有自己的特点,就是 可以调用自己
WITH RECURSIVE
CTE名称 AS (子查询)
SELECT|DELETE|UPDATE 语句;
递归公用表表达式由 2 部分组成,分别是种子查询和递归查询,中间通过关键字 UNION [ALL]进行连接。这里的 种子查询,意思就是获得递归的初始值 。这个查询只会运行一次,以创建初始数据集,之后递归查询会一直执行,直到没有任何新的查询数据产生,递归返回。
案例: 针对于我们常用的employees表,包含employee_id,last_name和manager_id三个字段。如果a是b的管理者,那么,我们可以把b叫做a的下属,如果同时b又是c的管理者,那么c就是b的下属,是a的下下属。
下面我们尝试用查询语句列出所有具有下下属身份的人员信息。如果用我们之前学过的知识来解决,会比较复杂,至少要进行 4 次查询才能搞定:
- 第一步,先找出初代管理者,就是不以任何别人为管理者的人,把结果存入临时表;
- 第二步,找出所有以初代管理者为管理者的人,得到一个下属集,把结果存入临时表;
- 第三步,找出所有以下属为管理者的人,得到一个下下属集,把结果存入临时表。
- 第四步,找出所有以下下属为管理者的人,得到一个结果集。
如果第四步的结果集为空,则计算结束,第三步的结果集就是我们需要的下下属集了,否则就必须继续进行第四步,一直到结果集为空为止。比如上面的这个数据表,就需要到第五步,才能得到空结果集。而且,最后还要进行第六步:把第三步和第四步的结果集合并,这样才能最终获得我们需要的结果集。
如果用递归公用表表达式,就非常简单了。我介绍下具体的思路。
- 用递归公用表表达式中的种子查询,找出初代管理者。字段 n 表示代次,初始值为 1 ,表示是第一代管理者。
- 用递归公用表表达式中的递归查询,查出以这个递归公用表表达式中的人为管理者的人,并且代次的值加 1 。直到没有人以这个递归公用表表达式中的人为管理者了,递归返回。
- 在最后的查询中,选出所有代次大于等于 3 的人,他们肯定是第三代及以上代次的下属了,也就是下下属了。这样就得到了我们需要的结果集。
这里看似也是 3 步,实际上是一个查询的 3 个部分,只需要执行一次就可以了。而且也不需要用临时表保存中间结果,比刚刚的方法简单多了。
WITH RECURSIVE cte
AS
(
SELECT employee_id,last_name,manager_id, 1 AS n FROM employees WHERE employee_id = 100 -- 种子查询,找到第一代领导
UNION ALL
SELECT a.employee_id,a.last_name,a.manager_id,n+ 1 FROM employees AS a JOIN cte
ON (a.manager_id = cte.employee_id) -- 递归查询,找出以递归公用表表达式的人为领导的人)
SELECT employee_id,last_name FROM cte WHERE n >= 3 ;
总之,递归公用表表达式对于查询一个有共同的根节点的树形结构数据,非常有用。它可以不受层级的限制,轻松查出所有节点的数据。如果用其他的查询方式,就比较复杂了。
公用表表达式的作用是可以替代子查询,而且可以被多次引用。递归公用表表达式对查询有一个共同根节点的树形结构数据非常高效,可以轻松搞定其他查询方式难以处理的查询。
十一、数据库的设计规范
1、概述
1.1 问题描述
- 用户都需要什么数据?需要在数据表中保存哪些数据?
- 如何保证数据表中数据的正确性,当插入、删除、更新的时候该进行怎样的约束检查?。如何降低数据表的数据冗余度,保证数据表不会因为用户量的增长而迅速扩张?
- 如何让负责数据库维护的人员更方便地使用数据库?
- 使用数据库的应用场景也各不相同,可以说针对不同的情况,设计出来的数据表可能千差万别
1.2 好的数据库设计原则
- 节省数据的存储空间
- 能够保证数据的完整性
- 方便进行数据库应用系统的开发
总之,开始设置数据库的时候,我们就需要重视数据表的设计。为了建立冗余较小、结构合理的数据库,设计数据库时必须遵循一定的规则。
2、范式
2.1 范式简介
在关系型数据库中,关于数据表设计的基本原则、规则就称为范式。可以理解为,一张数据表的设计结 构需要满足的某种设计标准的 级别 。要想设计一个结构合理的关系型数据库,必须满足一定的范式。
目前关系型数据库有六种常见范式,按照范式级别,从低到高分别是:第一范式(1NF)、第二范式 (2NF)、第三范式(3NF)、巴斯-科德范式(BCNF)、第四范式(4NF)和第五范式(5NF,又称完美范式)。数据库的范式设计越高阶,夯余度就越低,同时高阶的范式一定符合低阶范式的要求,满足最低要求的范式是第一范式(1NF)。在第一范式的基础上进一步满足更多规范的要求称为第二范式(2NF),其余范式以此类推。
一般来说,在关系型数据库设计中,最高也就遵循到BCNF, 普遍还是3NF。但也不绝对,有时候为了提高某些查询性能,我们还需要破坏范式规则,也就是反规范化
2.2 键和相关属性的概念
范式的定义会使用到主键和候选键,数据库中的键(Key)由一个或者多个属性组成。数据表中常用的几种键和属性的定义:
- 超键:能唯—标识元组的属性集叫做超键。
- 候选键:如果超键不包括多余的属性,那么这个超键就是候选键
- 主键:用户可以从候选键中选择一个作为主键。
- 外键︰如果数据表R1中的某属性集不是R1的主键,而是另一个数据表R2的主键,那么这个属性集就是数据表R1的外键。
- 主属性:包含在任一候选键中的属性称为主属性
- 非主属性:与主属性相对,指的是不包含在任何一个候选键中的属性。
通常,我们也将候选键称之为“码”,把主键也称为“主码”。因为键可能是由多个属性组成的,针对单个属性,我们还可以用主属性和非主属性来进行区分。
举例
球员表(player)
:球员编号 | 姓名 | 身份证号 | 年龄 | 球队编号
球队表(team)
:球队编号 | 主教练 | 球队所在地
- 超键 :对于球员表来说,超键就是包括球员编号或者身份证号的任意组合,比如(球员编号) (球员编号,姓名)(身份证号,年龄)等。
- 候选键 :就是最小的超键,对于球员表来说,候选键就是(球员编号)或者(身份证号)。
- 主键 :我们自己选定,也就是从候选键中选择一个,比如(球员编号)。
- 外键 :球员表中的球队编号。
- 主属性 、 非主属性 :在球员表中,主属性是(球员编号)(身份证号),其他的属性(姓名) (年龄)(球队编号)都是非主属性。
2.3 第一范式(1st NF)
第一范式主要确保数据库中每个字段的值必须具有原子性,也就是说数据表中每个字段的值为不可再次拆分的最小数据单元。我们在设计某个字段的时候,对于字段X来说,不能把字段X拆分成字段X-1和字段X-2。事实上,任何的DBMS都会满足第一范式的要求,不会将字段进行拆分。
2.4 第二范式(2nd NF)
第二范式要求,在满足第一范式的基础上,还要满足数据库里的每一条数据记录,都是可唯一标识的。而且所有非主键字段,都必须完全依赖主键,不能只依赖主键的一部分。如果知道主键的所有属性的值,就可以检索到任何元组(行)的任何属性的任何值。(要求中的主键,其实可以扩展替换为候选键)
第二范式(2NF)要求实体的属性完全依赖主关键字。如果存在不完全依赖,那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体,新实体与元实体之间是一对多的关系
2.5 第三范式(3rd NF)
第三范式是在第二范式的基础上,确保数据表中的每一个非主键字段都和主键字段直接相关,也就是说,要求数据表中的所有非主键字段不能依赖于其他非主键字段。(即,不能存在非主属性A依赖于非主属性B,非主属性B依赖于主键C的情况,即存在“A->B->C"的决定关系)通俗地讲,该规则的意思是所有非主键属性之间不能由依赖关系,必须相互独立。这里的主键可以扩展为候选键。
2.6 小结
关于数据表的设计,有三个范式要遵循。
- 第一范式(1NF),确保每列保持
原子性
。数据库的每一列都是不可分割的原子数据项,不可再分的最小数据单元,而不能是集合、数组、记录等非原子数据项。 - 第二范式(2NF),确保每列都和主键
完全依赖
。尤其在复合主键的情况下,非主键部分不应该依赖于部分主键。 - 第三范式(3NF)确保每列都和主键列
直接相关
,而不是间接相关
范式的优点:数据的标准化有助于消除数据库中的数据冗余
,第三范式(3NF)通常被认为在性能、扩展性和数据完整性方面达到了最好的平衡。
范式的缺点:范式的使用,可能降低查询的效率
。因为范式等级越高,设计出来的数据表就越多、越精细,数据的冗余度就越低,进行数据查询的时候就可能需要关联多张表
,这不但代价昂贵,也可能使一些索引策略无效
。
范式只是提出了设计的标准,实际上设计数据表时,未必一定要符合这些标准。开发中,我们会出现为了性能和读取效率违反范式化的原则,通过增加少量的冗余或重复的数据来提高数据库的读性能,减少关联查询,join 表的次数,实现空间换取时间的目的。因此在实际的设计过程中要理论结合实际,灵活运用。
3、反范式化
3.1 概述
有的时候不能简单按照规范要求设计数据表,因为有的数据看似冗余,其实对业务来说十分重要。这个时候,我们就要遵循业务优先的原则,首先满足业务需求,再尽量减少冗余。
如果数据库中的数据量比较大,系统的UV和PV访问频次比较高,则完全按照MysQL的三大范式设计数据表,读数据时会产生大量的关联查询,在一定程度上会影响数据库的读性能。如果我们想对查询效率进行优化,反范式优化也是一种优化思路。此时,可以通过在数据表中增加冗余字段来提高数据库的读性能。
规范化 vs 性能
- 为满足某种商业目标 , 数据库性能比规范化数据库更重要
- 在数据规范化的同时 , 要综合考虑数据库的性能
- 通过在给定的表中添加额外的字段,以大量减少需要从中搜索信息所需的时间
- 通过在给定的表中插入计算列,以方便查询
3.2 反范式的新问题
- 存储 空间变大了
- 一个表中字段做了修改,另一个表中冗余的字段也需要做同步修改,否则 数据不一致
- 若采用存储过程来支持数据的更新、删除等额外操作,如果更新频繁,会非常 消耗系统资源
- 在 数据量小 的情况下,反范式不能体现性能的优势,可能还会让数据库的设计更加复杂
3.3 反范式的适用场景
当冗余信息有价值或者能 大幅度提高查询效率 的时候,我们才会采取反范式的优化
- 增加冗余字段一定要符合如下两个条件。只要满足这两个条件,才可以考虑增加夯余字段。
- 这个冗余字段不需要经常进行修改
- 这个冗余字段查询的时候不可或缺
在现实生活中,我们经常需要一些冗余信息,比如订单中的收货人信息,包括姓名、电话和地址等。每 次发生的 订单收货信息
都属于 历史快照
,需要进行保存,但用户可以随时修改自己的信息,这时保存这 些冗余信息是非常有必要的。反范式优化也常用在 数据仓库
的设计中,因为数据仓库通常存储历史数据
,对增删改的实时性要求不 强,对历史数据的分析需求强。这时适当允许数据的冗余度,更方便进行数据分析。简单总结下数据仓库和数据库在使用上的区别:
- 数据库设计的目的在于
捕捉数据
,而数据仓库设计的目的在于分析数据
- 数据库对数据的
增删改实时性
要求强,需要存储在线的用户数据,而数据仓库存储的一般是历史数据
- 数据库设计需要
尽量避免冗余
,但为了提高查询效率也允许一定的冗余度
,而数据仓库在设计上更偏向采用反范式设计
4. BCNF(巴斯范式)
人们在3NF的基础上进行了改进,提出了巴斯范式(BCNF),页脚巴斯 - 科德范式(Boyce - Codd Normal Form)。BCNF被认为没有新的设计规范加入,只是对第三范式中设计规范要求更强,使得数据库冗余度更小。所以,称为是修正的第三范式,或扩充的第三范式,BCNF不被称为第四范式。
若一个关系达到了第三范式,并且它只有一个候选键,或者它的每个候选键都是单属性,则该关系自然达到BC范式。一般来说,一个数据库设符合3NF或者BCNF就可以了。
5、其他范式
5.1 第四范式
多值依赖的概念:
多值依赖
即属性之间的一对多关系,记为K—>—>A。函数依赖
事实上是单值依赖,所以不能表达属性值之间的一对多关系。平凡的多值依赖
:全集U=K+A,一个K可以对应于多个A,即K—>—>A。此时整个表就是一组一对多关系。非平凡的多值依赖
:全集U=K+A+B,一个K可以对应于多个A,也可以对应于多个B,A与B相互独立,即K—>—>A,K—>—>B。整个表有多组一对多关系,且有:"一"部分是相同的属性集合,“多”部分是相互独立的属性集合。
第四范式即在满足巴斯 - 科德范式(BCNF)的基础上,消除非平凡且非函数依赖的多值依赖(即把同一表的多对多关系删除)
5.2 第五范式、域键范式
除了第四范式外,我们还有更高级的第五范式(又称完美范式)和域键范式(DKNF)。在满足第四范式(4NF)的基础上,消除不是由候选键所蕴含的连接依赖。如果关系模式R中的每一个连 接依赖均由R的候选键所隐含,则称此关系模式符合第五范式。
函数依赖是多值依赖的一种特殊的情况,而多值依赖实际上是连接依赖的一种特殊情况。但连接依赖不 像函数依赖和多值依赖可以由 语义直接导出 ,而是在 关系连接运算 时才反映出来。存在连接依赖的关系 模式仍可能遇到数据冗余及插入、修改、删除异常等问题。第五范式处理的是 无损连接问题 ,这个范式基本 没有实际意义 ,因为无损连接很少出现,而且难以察觉。而域键范式试图定义一个 终极范式 ,该范式考虑所有的依赖和约束类型,但是实用价值也是最小的,只存在理论研究中。
6、实战案例
在实际工作场景中,这种由于数据表结构设计不合理,而导致的数据重复的现象并不少见。往往是系统虽然能够运行,承载能力却很差,稍微有点流量,就会出现内存不足、CPU使用率飙升的情况,甚至会导致整个项目失败
这个表中的字段很多,表里的数据量也很惊人。大量重复导致表变得庞大,效率极低。如何改?
6.1 迭代1次:考虑1NF
第一范式要求:所有的字段都是基本数据类型,不可进行拆分。这里需要确认,所有的列中,每个字段只包含一种数据。这张表里,我们把“property"这一字段,拆分成”specification (规格)" 和 “unit (单位)”
6.2 迭代2次:考虑2NF
第二范式要求,在满足第一范式的基础上,还要满足数据表里的每一条数据记录,都是可唯一标识的。而且所有字段,都必须完全依赖主键,不能只依赖主键的一部分
第1步,就是要确定这个表的主键。通过观察发现,字段“listnumber(单号)“+“barcode(条码)“可以唯一标识每一条记录,可以作为主键。第2步,确定好了主键以后,判断哪些字段完全依赖主键,哪些字段只依赖于主键的一部分。把只依赖于主键一部分的字段拆出去,形成新的数据表。此外,字段"supplierid(供应商编号)”“suppliername(供应商名称)”“stock(仓库)“只依赖于"listnumber(单号)”,不完全依赖于主键,所以,我们可以把"supplierid”“suppliername”“stock"这3个字段拆出去,再加上它们依赖的字段"listnumber(单号)”,就形成了一个新的表"进货单头表”。剩下的字段,会组成新的表,我们叫它"进货单明细表"
进货单头表
进货单明细表
商品信息表
6.3 迭代3次:考虑3NF
我们的进货单头表,还有数据冗余的可能。因为"suppliername"依赖"supplierid",那么就可以按照第三范式的原则进行拆分了。我们就进一步拆分进货单头表,把它拆解成供货商表和进货单头表
6.4 反范式化:业务优先的原则
因此,最后我们可以把进货单表拆分成下面的4个表
供货商表:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5tOUlGDa-1685002653004)(null)]
进货单头表:
进货单明细表:
商品信息表:
7、ER模型
在开发基于数据库的信息系统的设计阶段,通常使用ER模型来描述信息需求和信息特性,帮助我们理清业务逻辑,从而设计出优秀的数据库。
7.1 ER模型包括哪些要素?
ER 模型中有三个要素,分别是实体、属性和关系。
实体 ,可以看做是数据对象,往往对应于现实生活中的真实存在的个体。在 ER 模型中,用 矩形 来表 示。实体分为两类,分别是 强实体 和 弱实体 。强实体是指不依赖于其他实体的实体;弱实体是指对另 一个实体有很强的依赖关系的实体。
属性 ,则是指实体的特性。比如超市的地址、联系电话、员工数等。在 ER 模型中用 椭圆形 来表示。
关系 ,则是指实体之间的联系。比如超市把商品卖给顾客,就是一种超市与顾客之间的联系。在 ER 模 型中用 菱形 来表示。
注意:实体和属性不容易区分。这里提供一个原则:我们要从系统整体的角度出发去看,可以独立存在的是实体,不可再分的是属性。也就是说,属性不能包含其他属性。
7.2 关系的类型
在 ER 模型的 3 个要素中,关系又可以分为 3 种类型,分别是 一对一、一对多、多对多。
一对一 :指实体之间的关系是一一对应的,比如个人与身份证信息之间的关系就是一对一的关系。一个人只能有一个身份证信息,一个身份证信息也只属于一个人。
一对多 :指一边的实体通过关系,可以对应多个另外一边的实体。相反,另外一边的实体通过这个关系,则只能对应唯一的一边的实体。比如说,我们新建一个班级表,而每个班级都有多个学生,每个学 生则对应一个班级,班级对学生就是一对多的关系。
多对多 :指关系两边的实体都可以通过关系对应多个对方的实体。比如在进货模块中,供货商与超市之 间的关系就是多对多的关系,一个供货商可以给多个超市供货,一个超市也可以从多个供货商那里采购 商品。再比如一个选课表,有许多科目,每个科目有很多学生选,而每个学生又可以选择多个科目,这 就是多对多的关系
7.3 ER模型举例与转化
通过绘制 ER 模型,我们已经理清了业务逻辑,转换的原则:
- 一个 实体 通常转换成一个 数据表 ;
- 一个 多对多的关系 ,通常也转换成一个 数据表 ;
- 一个 1 对 1 ,或者 1 对多 的关系,往往通过表的 外键 来表达,而不是设计一个新的数据表;
- 属性 转换成表的 字段
7.4 总结
8、设计原则与建议
8.1 数据表的设计原则
综合以上内容,总结出数据表设计的一般原则:“三少一多”
- 数据表的个数越少越好
RDBMS的核心在于对实体和联系的定义,也就是E-R图(Entity Relationship Diagram),数据表越少,证明实体和联系设计得越简洁,既方便理解又方便操作
- 数据表中的字段个数越少越好
字段个数越多,数据冗余的可能性越大。设置字段个数少的前提是各个字段相互独立,而不是某个字段的取值可以由其他字段计算出来。当然字段个数少是相对的,我们通常会在数据冗余和检索效率中进行平衡。
- 数据表中联合主键的字段个数越少越好
设置主键是为了确定唯一性,当一个字段无法确定唯一性的时候,就需要采用联合主键的方式(也就是用多个字段来定义一个主键)。联合主键中的字段越多,占用的索引空间越大,不仅会加大理解难度,还会增加运行时间和索引空间,因此联合主键的字段个数越少越好。
- 使用主键和外键越多越好
数据库的设计实际上就是定义各种表,以及各种字段之间的关系。这些关系越多,证明这些实体之间的冗余度越低,利用度越高。这样做的好处在于不仅保证了数据表之间的独立性,还能提升相互之间的关联使用率。
8.2 数据库对象编写建议
1、关于库
- 【强制】库的名称必须控制在32个字符以内,只能使用英文字母、数字和下划线,建议以英文字 母开头。
- 【强制】库名中英文 一律小写 ,不同单词采用 下划线 分割。须见名知意。
- 【强制】库的名称格式:业务系统名称_子系统名。
- 【强制】库名禁止使用关键字(如type,order等)。
- 【强制】创建数据库时必须 显式指定字符集 ,并且字符集只能是utf8或者utf8mb4。 创建数据库SQL举例:CREATE DATABASE crm_fund DEFAULT CHARACTER SET ‘utf8’ ;
- 【建议】对于程序连接数据库账号,遵循 权限最小原则 使用数据库账号只能在一个DB下使用,不准跨库。程序使用的账号 原则上不准有drop权限 。
- 【建议】临时库以 tmp_ 为前缀,并以日期为后缀; 备份库以 bak_ 为前缀,并以日期为后缀
2、关于表、列
-
【强制】表和列的名称必须控制在32个字符以内,表名只能使用英文字母、数字和下划线,建议 以 英文字母开头 。
-
【强制】 表名、列名一律小写 ,不同单词采用下划线分割。须见名知意。
-
【强制】表名要求有模块名强相关,同一模块的表名尽量使用 统一前缀 。比如:crm_fund_item
-
【强制】创建表时必须 显式指定字符集 为utf8或utf8mb4。
-
【强制】表名、列名禁止使用关键字(如type,order等)。
-
【强制】创建表时必须 显式指定表存储引擎 类型。如无特殊需求,一律为InnoDB。
-
【强制】建表必须有comment。
-
【强制】字段命名应尽可能使用表达实际含义的英文单词或 缩写 。如:公司 ID,不要使用 corporation_id, 而用corp_id 即可。
-
【强制】布尔值类型的字段命名为 is_描述 。如member表上表示是否为enabled的会员的字段命 名为 is_enabled。
-
【强制】禁止在数据库中存储图片、文件等大的二进制数据 通常文件很大,短时间内造成数据量快速增长,数据库进行数据库读取时,通常会进行大量的随 机IO操作,文件很大时,IO操作很耗时。通常存储于文件服务器,数据库只存储文件地址信息。
-
【建议】建表时关于主键: 表必须有主键
(1)强制要求主键为id,类型为int或bigint,且为 auto_increment 建议使用unsigned无符号型。
(2)标识表里每一行主体的字段不要设为主键,建议 设为其他字段如user_id,order_id等,并建立unique key索引。因为如果设为主键且主键值为随机 插入,则会导致innodb内部页分裂和大量随机I/O,性能下降。
-
【建议】核心表(如用户表)必须有行数据的 创建时间字段 (create_time)和 最后更新时间字段 (update_time),便于查问题。
-
【建议】表中所有字段尽量都是 NOT NULL 属性,业务可以根据需要定义 DEFAULT值 。 因为使用 NULL值会存在每一行都会占用额外存储空间、数据迁移容易出错、聚合函数计算结果偏差等问 题。
-
【建议】所有存储相同数据的 列名和列类型必须一致 (一般作为关联列,如果查询时关联列类型 不一致会自动进行数据类型隐式转换,会造成列上的索引失效,导致查询效率降低)。
-
【建议】中间表(或临时表)用于保留中间结果集,名称以 tmp_ 开头。 备份表用于备份或抓取源表快照,名称以 bak_ 开头。中间表和备份表定期清理。 1
-
【示范】一个较为规范的建表语句
CREATE TABLE user_info (
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
`user_id` bigint(11) NOT NULL COMMENT '用户id',
`username` varchar(45) NOT NULL COMMENT '真实姓名',
`email` varchar(30) NOT NULL COMMENT '用户邮箱',
`nickname` varchar(45) NOT NULL COMMENT '昵称',
`birthday` date NOT NULL COMMENT '生日',
`sex` tinyint(4) DEFAULT '0' COMMENT '性别',
`short_introduce` varchar(150) DEFAULT NULL COMMENT '一句话介绍自己,最多50个汉字',
`user_resume` varchar(300) NOT NULL COMMENT '用户提交的简历存放地址',
`user_register_ip` int NOT NULL COMMENT '用户注册时的源ip',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE
CURRENT_TIMESTAMP COMMENT '修改时间',
`user_review_status` tinyint NOT NULL COMMENT '用户资料审核状态,1为通过,2为审核中,3为未
通过,4为还未提交审核',
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_user_id` (`user_id`),
KEY `idx_username`(`username`),
KEY `idx_create_time_status`(`create_time`,`user_review_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='网站用户基本信息
- 【建议】创建表时,可以使用可视化工具。这样可以确保表、字段相关的约定都能设置上。实际上,我们通常很少自己写 DDL 语句,可以使用一些可视化工具来创建和操作数据库和数据表。可视化工具除了方便,还能直接帮我们将数据库的结构定义转化成 SQL 语言,方便数据库和数据表结构的导出和导入。
3、关于索引
- 【强制】InnoDB表必须主键为id int/bigint auto_increment,且主键值 禁止被更新 。
- 【强制】InnoDB和MyISAM存储引擎表,索引类型必须为 BTREE 。
- 【建议】主键的名称以 pk_ 开头,唯一键以 uni_ 或 uk_ 开头,普通索引以 idx_ 开头,一律使用小写格式,以字段的名称或缩写作为后缀。
- 【建议】多单词组成的columnname,取前几个单词首字母,加末单词组成column_name。如: sample 表 member_id 上的索引:idx_sample_mid。
- 【建议】单个表上的索引个数 不能超过6个 。
- 【建议】在建立索引时,多考虑建立 联合索引 ,并把区分度最高的字段放在最前面。
- 【建议】在多表 JOIN 的SQL里,保证被驱动表的连接列上有索引,这样JOIN 执行效率最高。
- 【建议】建表或加索引时,保证表里互相不存在 冗余索引 。 比如:如果表里已经存在key(a,b), 则key(a)为冗余索引,需要删除。
4、SQL编写
- 【强制】程序端SELECT语句必须指定具体字段名称,禁止写成 *。
- 【建议】程序端insert语句指定具体字段名称,不要写成INSERT INTO t1 VALUES(…)。
- 【建议】除静态表或小表(100行以内),DML语句必须有WHERE条件,且使用索引查找。
- 【建议】INSERT INTO…VALUES(XX),(XX),(XX)… 这里XX的值不要超过5000个。 值过多虽然上线很 快,但会引起主从同步延迟。
- 【建议】SELECT语句不要使用UNION,推荐使用UNION ALL,并且UNION子句个数限制在5个以 内。
- 【建议】线上环境,多表 JOIN 不要超过5个表。
- 【建议】减少使用ORDER BY,和业务沟通能不排序就不排序,或将排序放到程序端去做。ORDER BY、GROUP BY、DISTINCT 这些语句较为耗费CPU,数据库的CPU资源是极其宝贵的。
- 【建议】包含了ORDER BY、GROUP BY、DISTINCT 这些查询的语句,WHERE 条件过滤出来的结果 集请保持在1000行以内,否则SQL会很慢。
- 【建议】对单表的多次alter操作必须合并为一次 对于超过100W行的大表进行alter table,必须经过DBA审核,并在业务低峰期执行,多个alter需整 合在一起。 因为alter table会产生 表锁 ,期间阻塞对于该表的所有写入,对于业务可能会产生极 大影响。
- 【建议】批量操作数据时,需要控制事务处理间隔时间,进行必要的sleep。
- 【建议】事务里包含SQL不超过5个。 因为过长的事务会导致锁数据较久,MySQL内部缓存、连接消耗过多等问题。
- 【建议】事务里更新语句尽量基于主键或UNIQUE KEY,如UPDATE… WHERE id=XX; 否则会产生间隙锁,内部扩大锁定范围,导致系统性能下降,产生死锁。
8.3 PowerDesigner的使用
PowerDesigner是一款开发人员常用的数据库建模工具,用户利用该软件可以方便地制作 数据流程图 、 概念数据模型 、 物理数据模型 ,它几乎包括了数据库模型设计的全过程,是Sybase公司为企业建模和设 计提供的一套完整的集成化企业级建模解决方案。
参考:
https://www.bilibili.com/video/BV1iq4y1u7vj
https://new-wangz.github.io/categories/