MySQL8.0基础篇
一、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登录
1 | # 启动 MySQL 服务命令 |
3.3 MySQL演示使用
1 | # 查看所有数据库 |
- information_schema是 MySQL 系统自带的数据库,主要保存 MySQL 数据库服务器的系统信息,比如数据库的名称、数据表的名称、字段名称、存取权限、数据文件 所在的文件夹和系统使用的文件夹,等等。
- performance_schema是 MySQL 系统自带的数据库,可以用来监控 MySQL 的各类性能指标。
- sys数据库是 MySQL 系统自带的数据库,主要作用是以一种更容易被理解的方式展示 MySQL 数据库服务器的各类性能指标,帮助系统管理员和开发人员监控 MySQL 的技术性能。
- mysql数据库保存了 MySQL 数据库服务器运行时需要的系统信息,比如数据文件夹、当前使用的字符集、约束检查信息,等等。
1 | # 数据库的默认编码改为utf8mb4,见下面的ini修改,记得重启服务 |
MySQL图形化管理工具极大地方便了数据库的操作与管理,常用的图形化管理工具有:MySQL Workbench、phpMyAdmin、Navicat Preminum、MySQLDumper、SQLyog、dbeaver、MySQL ODBCConnector(推荐navicate)
1 | # 如果图形化连接出现错误,原因是MySQL8之前的版本中加密规则是mysql_native_password,而在MySQL8之后,加密规则是caching_sha2_password |
如果root密码忘记
1 | # 通过任务管理器或者服务管理,关掉mysqld(服务进程) |
3.4 MySQL目录结构与源码
MySQL的目录结构 | 说明 |
---|---|
bin目录 | 所有MySQL的可执行文件。如:mysql.exe |
MySQLInstanceConfig.exe | 数据库的配置向导,在安装时出现的内容 |
data目录 | 系统数据库所在的目录 |
my.ini文件 | MySQL的主要配置文件 |
D:\software\mysql\data | 用户创建的数据库所在的目录 |
这是我的my.ini配置文件,
1 | [mysqld] |
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 关键字、函数名、绑定变量等都大写
注释
1 | 单行注释:#注释文字(MySQL特有的方式) |
命名规则(暂时了解)
- 数据库、表名不得超过 30 个字符,变量名限制为 29 个
- 必须只能包含 A–Z, a–z, 0–9, _共 63 个字符
- 数据库名、表名、字段名等对象名中间不要包含空格
- 同一个MySQL软件中,数据库不能同名;同一个库中,表不能重名;同一个表中,字段不能重名
- 必须保证你的字段没有和保留字、数据库系统或常用方法冲突。如果坚持使用,请在SQL语句中使用`(着重号)引起来
- 保持字段名和类型的一致性,在命名字段并为其指定数据类型的时候一定要保证一致性。假如数据类型在一个表里是整数,那在另一个表里可就别变成字符型了
数据导入指令
1 | # 数据导入指令,需要用命令行 |
2、 基本的SELECT语句
1 | SELECT * FROM departments; |
- Field:表示字段名称。
- Type:表示字段类型,这里 barcode、goodsname 是文本型的,price 是整数类型的。
- Null:表示该列是否可以存储NULL值。
- Key:表示该列是否已编制索引。
- PRI表示该列是表主键的一部分;
- UNI表示该列是UNIQUE索引的一 部分;
- MUL表示在列中某个给定值允许出现多次。
- Default:表示该列是否有默认值,如果有,那么值是多少。
- Extra:表示可以获取的与给定列有关的附加信息,例如AUTO_INCREMENT等
3、运算符
1 | -- DUAL 伪表 |
1 | -- 最小值运算符 |
逻辑运算符主要用来判断表达式的真假,在MySQL中,逻辑运算符的返回结果为1、0或者NULL
1 | -- 逻辑非(NOT或!)运算符表示当给定的值为 0 时返回 1 ;当给定的值为非 0 值时返回 0 ;当给定的值为NULL时,返回NULL |
位运算符是在二进制数上进行计算的运算符。位运算符会先将操作数变成二进制数,然后进行位运算, 最后将计算结果从二进制变回十进制数
1 | -- 按位与(&)运算符将给定值对应的二进制数逐位进行逻辑与运算。当给定值对应的二进制位的数值都为 1 时,则该位返回 1 ,否则返回 0 。 |
4、排序和分页
4.1 排序规则
- 使用 ORDER BY 子句排序
- ASC(ascend): 升序
- DESC(descend):降序
- ORDER BY 子句在SELECT语句的结尾,列的别名只能在order by使用,不能在where使用,where需要在from后面
1 | -- 单列排序 |
4.2 分页
1 | --前10条记录: |
LIMIT 子句必须放在整个SELECT语句的最后,约束返回结果的数量可以 减少数据表的网络传输量 ,也可以提升查询效率
5、多表查询(重要)
1 | -- 自连接 |
七种SQL JOINS的实现
SQL99语法的新特性(了解即可)
SQL99 在 SQL92 的基础上提供了一些特殊语法,比如 NATURAL JOIN 用来表示自然连接。我们可以把 自然连接理解为 SQL92 中的等值连接。它会帮你自动查询两张连接表中所有相同的字段 ,然后进行 等值 连接(MySQL复杂链接 (左/右/全外)链接只能使用SQL99语法,SQL92不支持)
1 | -- SQL92 |
当我们进行连接的时候,SQL99还支持使用 USING 指定数据表里的 同名字段 进行等值连接。但是只能配 合JOIN一起使用。比如:
1 | SELECT employee_id,last_name,department_name |
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()
1 | -- AVG和SUM函数 |
7.2 GROUP BY
可以使用GROUP BY子句将表中的数据分成若干组,WHERE一定放在FROM后面
1 | SELECT column, group_function(column) |
7.3 HAVING
过滤分组:HAVING子句、行已经被分组、使用了聚合函数、满足HAVING 子句中条件的分组将被显示、HAVING 不能单独使用,必须要跟 GROUP BY 一起使用
1 | SELECT department_id, MAX(salary) |
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 | #方式 1 : |
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 子查询基本使用
- 查询(内查询)在主查询之前一次执行完成
- 子查询的结果被主查询(外查询)使用
注意事项
- 子查询要包含在括号内
- 将子查询放在比较条件的右侧
- 单行操作符对应单行子查询,多行操作符对应多行子查询
1 | -- 方式一: |
子查询的分类
我们按内查询的结果返回一条还是多条记录,将子查询分为单行子查询、多行子查询
我们按内查询是否被执行多次,将子查询划分为相关(或关联)子查询和不相关(或非关联)子查询。子查询从数据表中查询了数据结果,如果这个数据结果只执行一次,然后这个数据结果作为主查询的条件进行执行,那么这样的子查询叫做不相关子查询。同样,如果子查询需要执行多次,即采用循环的方式,先从外部查询开始,每次都传入子查询进行查询,然后再将结果反馈给外部,这种嵌套的执行方式就称为相关子查询
8.1 单行子查询
1 | -- 查询与 141 号或 174 号员工的manager_id和department_id相同的其他员工的employee_id,manager_id,department_id |
8.2 多行子查询
操作符 | 含义 |
---|---|
IN | 等于列表中的任意一个 |
ANY | 需要和单行比较操作符一起使用,和子查询返回的 某一个 值比较 |
ALL | 需要和单行比较操作符一起使用,和子查询返回的 所有 值比较 |
SOME | 实际上是ANY的别名,作用相同,一般常使用ANY |
1 | -- mysql聚合函数不能嵌套使用 |
8.3 相关子查询
如果子查询的执行依赖于外部查询,通常情况下都是因为子查询中的表用到了外部的表,并进行了条件关联,因此每执行一次外部查询,子查询都要重新计算一次,这样的子查询就称之为关联子查询。相关子查询按照一行接一行的顺序执行,主查询的每一行都执行一次子查询
1 | -- 查询员工的id,salary,按照department_name 排序 |
三、创建和管理表
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、创建和管理数据库
1 | -- 创建数据库 |
3、创建表
创建表必须具备CREATE TABLE权限和存储空间
1 | -- 第一种方式创建 |
4、修改表
1 | -- 修改表指的是修改数据库中已经存在的数据表的结构 |
5、删除表
1 | -- 删除表 |
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错误日志中。
1 | CREATE DATABASE mytest; |
四、数据处理之增删改
1、插入数据
1 | -- VALUES的方式添加 |
2、更新数据
1 | UPDATE table_name |
3、删除数据
1 | -- table_name指定要执行删除操作的表;“[WHERE ]”为可选参数,指定删除条件,如果没有WHERE子句,DELETE语句将删除表中的所有记录 |
4、MySQL 8 新特性:计算列
什么叫计算列呢?简单来说就是某一列的值是通过别的列计算得来的。例如,a列值为 1 、b列值为 2 ,c列不需要手动插入,定义a+b的结果为c的值,那么c就是计算列,是通过别的列计算得来的
1 | -- 举例:定义数据表tb1,然后定义字段id、字段a、字段b和字段c,其中字段c为计算列,用于计算a+b的值。 首先创建测试表tb1 |
5、综合案例
1 | #1、创建数据库test01_library |
五、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 。
1 | CREATE TABLE test_bit1( |
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则只能反映出插入时当地的时区,其他时区的人查看数据必然会有误差的
1 | -- 一般存注册时间、商品发布时间等,不建议使用DATETIME存储,而是使用时间戳,其他建议datetime |
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 个
1 | CREATE TABLE test_enum( |
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类型不同。
1 | CREATE TABLE temp_mul( |
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类型的数据更加方便和高效
1 | CREATE TABLE 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 默认值约束
1 | -- 注意: MySQL不支持check约束,但可以使用check约束,而没有任何效果 |
2、非空约束
NOT NULL,限定某个字段/某列的值不允许为空
- 默认,所有的类型的值都可以是NULL,包括INT、FLOAT等数据类型
- 非空约束只能出现在表对象的列上,只能某个列单独限定非空,不能组合非空
- 一个表可以有很多列都分别限定了非空
- 空字符串’'不等于NULL, 0 也不等于NULL
1 | CREATE TABLE 表名称( |
3、唯一性约束
UNIQUE,用来限制某个字段/某列的值不能重复。
- 同一个表可以有多个唯一约束。
- 唯一约束可以是某一个列的值唯一,也可以多个列组合的值唯一。
- 唯一性约束允许列值为空。
- 在创建唯一约束的时候,如果不给唯一约束命名,就默认和列名相同。
- MySQL会给唯一约束的列上默认创建一个唯一索引。
1 | -- 建表 |
4、PRIMARY KEY 约束
primary key,用来唯一标识表中的一行记录
- 一个表最多只能有一个主键约束,建立主键约束可以在列级别创建,也可以在表级别上创建。
- 主键约束对应着表中的一列或者多列(复合主键)
- 如果是多列组合的复合主键约束,那么这些列都不允许为空值,并且组合的值不允许重复。
- MySQL的主键名总是PRIMARY ,就算自己命名了主键约束名也没用。
- 当创建主键约束时,系统默认会在所在的列或列组合上建立对应的 主键索引 (能够根据主键查询的,就根据主键查询,效率更高)。如果删除主键约束了,主键约束对应的索引就自动删除了。
- 需要注意的一点是,不要修改主键字段的值。因为主键是数据记录的唯一标识,如果修改了主键的值,就有可能会破坏数据的完整性。
1 | -- 创建 |
5、AUTO_INCREMENT
auto_increment,某个字段的值自增
- 一个表最多只能有一个自增长列
- 当需要产生唯一标识符或顺序值时,可设置自增长
- 自增长列约束的列必须是键列(主键列,唯一键列)
- 自增约束的列的数据类型必须是整数类型
- 如果自增列指定了 0 和 null,会在当前最大值的基础上自增;如果自增列手动指定了具体值,直接赋值为具体值
1 | -- 建表时 |
在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 简单演示
1 | -- 建表时 |
- 添加了外键约束后,主表的修改和删除数据受约束
- 添加了外键约束后,从表的添加和修改数据受约束
- 在从表上建立外键,要求主表必须存在
- 删除主表时,要求从表从表先删除,或将从表中外键引用该主表的关系先删除
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 的方式。
1 | -- 举例 |
6.4 删除外键约束
1 | -- 第一步先查看约束名和删除外键约束 |
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约束了
1 | create table employee( |
8、DEFAULT约束
给某个字段/某列指定默认值,一旦设置默认值,在插入数据时,如果此字段没有显式赋值,则赋值为默认值。
1 | -- 建表 |
七、视图
1、常见的数据库对象
对象 | 描述 |
---|---|
表(TABLE) | 表是存储数据的逻辑单元,以行和列的形式存在,列就是字段,行就是记录 |
数据字典 | 就是系统表,存放数据库相关信息的表。系统表的数据通常由数据库系统维护,程序员通常不应该修改,只可查看 |
约束(CONSTRAINT) | 执行数据校验的规则,用于保证数据完整性的规则 |
视图(VIEW) | 一个或者多个数据表里的数据的逻辑显示,视图并不存储数据 |
索引(INDEX) | 用于提高查询性能,相当于书的目录 |
存储过程(PROCEDURE) | 用于完成一次完整的业务处理,没有返回值,但可通过传出参数将多个值传给调用环境 |
存储函数(FUNCTION) | 用于完成一次特定的计算,具有一个返回值 |
触发器(TRIGGER) | 相当于一个事件监听器,当数据库发生特定事件后,触发器被触发,完成相应的处理 |
2、视图概述
- 视图是一种虚拟表,本身是不具有数据的,占用很少的内存空间,它是 SQL 中的一个重要概念。
- 视图建立在已有表的基础上 , 视图赖以建立的这些表称为 基表
- 视图的创建和删除只影响视图本身,不影响对应的基表。但是当对视图中的数据进行增加、删除和修改操作时,数据表中的数据会相应地发生变化,反之亦然。
- 向视图提供数据内容的语句为 SELECT 语句, 可以将视图理解为 存储起来的 SELECT 语句
- 在数据库中,视图不会保存数据,数据真正保存在数据表中。当对视图中的数据进行增加、删除和修改操作时,数据表中的数据会相应地发生变化;反之亦然。
- 视图,是向用户提供基表数据的另一种表现形式。通常情况下,小型项目的数据库可以不使用视图,但是在大型项目中,以及数据表比较复杂的情况下,视图的价值就凸显出来了,它可以帮助我们把经常查询的结果集放到虚拟表中,提升使用效率。理解和使用起来都非常方便。
3、创建视图
1 | -- 在 CREATE VIEW 语句中嵌入子查询 |
4、查看视图
1 | -- 查看数据库的表对象、视图对象 |
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、修改、删除视图
1 | -- 修改视图 |
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 语法分析
1 | CREATE PROCEDURE 存储过程名(IN|OUT|INOUT 参数名 参数类型,...) |
说明:
- 参数前面的符号的意思
- IN:当前参数为输入参数,也就是表示入参;存储过程只是读取这个参数的值。如果没有定义参数种类,默认就是 IN,表示输入参数。
- OUT:当前参数为输出参数,也就是表示出参;执行完成之后,调用这个存储过程的客户端或者应用程序就可以读取这个参数返回的值了。
- INOUT:当前参数既可以为输入参数,也可以为输出参数。
- 形参类型可以是 MySQL数据库中的任意类型。
- characteristics 表示创建存储过程时指定的对存储过程的约束条件
1 | LANGUAGE SQL |
- 存储过程体中可以有多条 SQL 语句,如果仅仅一条SQL 语句,则可以省略 BEGIN 和 END
编写存储过程并不是一件简单的事情,可能存储过程中需要复杂的 SQL 语句。 - 需要设置新的结束标记,因为MySQL默认的语句结束符号为分号‘;’。为了避免与存储过程中SQL语句结束符相冲突,需要使用DELIMITER改变存储过程的结束符。
2.2 举例
1 | -- 举例1 |
3、调用存储过程
1 | -- 存储过程有多种调用方法。存储过程必须使用CALL语句调用,并且存储过程和数据库相关,如果要执行其他数据库中的存储过程,需要指定数据库名称 |
4、存储函数的使用
4.1 语法分析
1 | CREATE FUNCTION 函数名(参数名 参数类型,...) |
- 参数列表:指定参数为IN、OUT或INOUT只对PROCEDURE是合法的,FUNCTION中总是默认为IN参数。
- RETURNS type 语句表示函数返回数据的类型;
RETURNS子句只能对FUNCTION做指定,对函数而言这是强制的。它用来指定函数的返回类型,而且函数体必须包含一个RETURN value语句。 - characteristic 创建函数时指定的对函数的约束。取值与创建存储过程时相同,这里不再赘述。
- 函数体也可以用BEGIN…END来表示SQL代码的开始和结束。如果函数体只有一条语句,也可以省略BEGIN…END。
4.2 调用存储函数
在MySQL中,存储函数的使用方法与MySQL内部函数的使用方法是一样的。换言之,用户自己定义的存储函数与MySQL内部函数是一个性质的。区别在于,存储函数是用户自己定义的,而内部函数是MySQL的开发者定义的
1 | SELECT 函数名(实参列表) |
注意:
若在创建存储函数中报错“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、存储过程和函数的查看、修改、删除
1 | -- 查看存储过程和函数的创建信息 |
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。
1 | -- 查看系统变量 |
1.2 用户变量
用户变量是用户自己定义的,作为 MySQL 编码规范,MySQL 中的用户变量以一个“@”开头。根据作用范围不同,又分为会话用户变量和局部变量。
- 会话用户变量:作用域和会话变量一样,只对当前连接会话有效。
- 局部变量:只在 BEGIN 和 END 语句块中有效。局部变量只能在存储过程和函数中使用。
1 | -- 会话用户变量 |
1.3 MySQL 8. 0 的新特性—全局变量的持久化
使用SET GLOBAL语句设置的变量值只会临时生效。数据库重启后,服务器又会从MySQL配置文件中读取变量的默认值。 MySQL 8.0版本新增了SET PERSIST命令。例如,设置服务器的最大连接数为 1000 :
1 | SET PERSIST global max_connections = 1000 ; |
MySQL会将该命令的配置保存到数据目录下的mysqld-auto.cnf文件中,下次启动时会读取该文件,用其中的配置来覆盖默认的配置文件。
2、定义条件与处理程序
定义条件是事先定义程序执行过程中可能遇到的问题,处理程序定义了在遇到问题时应当采取的处理方式,并且保证存储过程或函数在遇到警告或错误时能继续执行。这样可以增强存储程序处理问题的能力,避免程序异常停止运行。说明:定义条件和处理程序在存储过程、存储函数中都是支持的。
2.1 定义条件
定义条件就是给MySQL中的错误码命名,这有助于存储的程序代码更清晰。它将一个错误名字和指定的错误条件关联起来。这个名字可以随后被用在定义处理程序的DECLARE HANDLER语句中。定义条件使用DECLARE语句,语法格式如下:
1 | 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。
1 | -- 举例 定义”ERROR 1148(42000)”错误,名称为command_not_allowed |
2.2 定义处理程序
可以为SQL执行过程中发生的某种类型的错误定义特殊的处理程序。定义处理程序时,使用DECLARE语句的语法如下:
1 | 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 | -- 方法 1 :捕获sqlstate_value |
2.3 案例解决
在存储过程中,定义处理程序,捕获sqlstate_value值,当遇到MySQL_error_code值为 1048 时,执行CONTINUE操作,并且将@proc_value的值设置为-1
1 | DELIMITER // |
3、流程控制
解决复杂问题不可能通过一个 SQL 语句完成,我们需要执行多个 SQL 操作。流程控制语句的作用就是控制存储过程中 SQL 语句的执行顺序,是我们完成复杂操作必不可少的一部分。只要是执行的程序,流程就分为三大类:
- 顺序结构:程序从上往下依次执行
- 分支结构:程序按条件进行选择执行,从两条或多条路径中选择一条执行
- 循环结构:程序满足一定条件下,重复执行一组语句
针对于MySQL 的流程控制语句主要有 3 类。注意:只能用于存储程序。
- 条件判断语句:IF 语句和 CASE 语句
- 循环语句:LOOP、WHILE 和 REPEAT 语句
- 跳转语句:ITERATE 和 LEAVE 语句
1 | -- 分支结构之 IF |
4、游标
4.1 什么是游标(或光标)
虽然我们也可以通过筛选条件 WHERE 和 HAVING,或者是限定返回记录的关键字 LIMIT 返回一条记录,但是,却无法在结果集中像指针一样,向前定位一条记录、向后定位一条记录,或者是随意定位到某一条记录,并对记录的数据进行处理。这个时候,就可以用到游标。游标,提供了一种灵活的操作方式,让我们能够对结果集中的每一条记录进行定位,并对指向的记录中的数据进行操作的数据结构。 游标让 SQL 这种面向集合的语言有了面向过程开发的能力。
在 SQL 中,游标是一种临时的数据库对象,可以指向存储在数据库表中的数据行指针。这里游标充当了指针的作用,我们可以通过操作游标来对数据行进行操作。MySQL中游标可以在存储过程和函数中使用。
4.2 游标的使用
1 | -- 1、声明游标 |
4.3 小结
游标是 MySQL 的一个重要的功能,为逐条读取结果集中的数据,提供了完美的解决方案。跟在应用层面实现相同的功能相比,游标可以在存储程序中使用,效率高,程序也更加简洁。但同时也会带来一些性能问题,比如在使用游标的过程中,会对数据行进行加锁,这样在业务并发量大的时候,不仅会影响业务之间的效率,还会消耗系统资源,造成内存不足,这是因为游标是在内存中进行的处理。
建议:养成用完之后就关闭的习惯,这样才能提高系统的整体效率。
5、触发器
5.1 触发器概述
MySQL从5.0.2版本开始支持触发器。MySQL的触发器和存储过程一样,都是嵌入到MySQL服务器的一段程序。触发器是由事件来触发某个操作,这些事件包括INSERT、UPDATE、DELETE事件。所谓事件就是指用户的动作或者触发某项行为。如果定义了触发程序,当数据库执行这些语句时候,就相当于事件发生了,就会自动激发触发器执行相应的操作。当对数据表中的数据执行插入、更新和删除操作,需要自动执行一些数据库逻辑时,可以使用触发器来实现。
5.2 触发器应用场景
1 | -- 创建触发器的语法结构 |
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 使用窗口函数前后对比
1 | -- 假设我现在有这样一个数据表,它显示了某购物网站在每个城市每个区的销售额 |
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子句:为分区中的某个子集定义规则,可以用来作为滑动窗口使用
1 | -- 窗口函数的语法结构 |
2.4 举例讲解
首先创建表和插入数据
1 | CREATE TABLE goods( |
不同函数讲解
1 | -- ====================序号函数========================== |
窗口函数的特点是可以分组,而且可以在分组内排序。另外,窗口函数不会因为分组而减少原表中的行数,这对我们在原表数据的基础上进行统计和排序非常有用
3、新特性 2 :公用表表达式
公用表表达式(或通用表表达式)简称为CTE(Common Table Expressions)。CTE是一个命名的临时结果集,作用范围是当前语句。CTE可以理解成一个可以复用的子查询,当然跟子查询还是有点区别的,CTE可以引用其他CTE,但子查询不能引用其他子查询。所以,可以考虑代替子查询。依据语法结构和执行方式的不同,公用表表达式分为普通公用表表达式和递归公用表表达式 2 种
3.1 普通公用表表达式
1 | -- 普通公用表表达式的语法结构 |
3.2 递归公用表表达式
递归公用表表达式也是一种公用表表达式,只不过,除了普通公用表表达式的特点以外,它还有自己的特点,就是 可以调用自己
1 | WITH RECURSIVE |
递归公用表表达式由 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 个部分,只需要执行一次就可以了。而且也不需要用临时表保存中间结果,比刚刚的方法简单多了。
1 | WITH RECURSIVE cte |
总之,递归公用表表达式对于查询一个有共同的根节点的树形结构数据,非常有用。它可以不受层级的限制,轻松查出所有节点的数据。如果用其他的查询方式,就比较复杂了。
公用表表达式的作用是可以替代子查询,而且可以被多次引用。递归公用表表达式对查询有一个共同根节点的树形结构数据非常高效,可以轻松搞定其他查询方式难以处理的查询。
十一、数据库的设计规范
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个表
供货商表:
进货单头表:
进货单明细表:
商品信息表:
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
-
【示范】一个较为规范的建表语句
1 | CREATE TABLE user_info ( |
- 【建议】创建表时,可以使用可视化工具。这样可以确保表、字段相关的约定都能设置上。实际上,我们通常很少自己写 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/](