【MySQL】MySQL 基础
1. 初识 MySQL
- DB:DataBase,数据库,实际上在硬盘上以文件的形式存在
- SQL:Structured Query Language,结构化查询语言,是一门标准通用的语言,标准sql适用于所有的数据库产品,SQL语句在执行时,内部会先进行编译,然后再执行。(SQL语句的编译由DBMS完成)
- DBMS: DataBase Management System,数据库管理系统,如MySQL,Oracle,DB2等,用来管理DB(用来管理数据库中的数据)
DBMS负责执行sql语句,其通过执行sql语句来操作DB中的数据。
1.1 启动 MySQL 服务
命令行启动MySQL服务(管理员模式)
1 | net start mysql |
1.2 连接数据库
命令行连接数据库(管理员模式)
1 | mysql -u root -p xxxxx -- 连接数据库 |
1.3 SQL 命令
更改用户密码并刷新权限(sql语句需要以;结尾)
1 | UPDATE mysql.user SET authentication_string=password('123456') |
基本命令
1 | SHOW DATABASES; -- 查询所有数据库 |
1.4 数据库语言分类
- DDL: Database Definition Language 数据库定义语言。用于对表的结构进行增删改:CREATE DROP ALTER
- DML:Data Manipulation Language 数据库操纵语言。用于对表的数据进行增删改:INSERT DELETE UPDATE
- DQL:Data Query Language 数据库查询语言。用于查询表中的数据,基本结构是由SELECT子句,FROM子句,WHERE子句组成的查询块:
- DCL:Data Control Language 数据库控制语言。用来授予或回收访问数据库的某种特权:GRANT REVOKE
- TCL:Transaction Control Language 事务控制语言。控制数据库操纵事务发生的时间及效果,对数据库实行监视等:COMMIT ROLLBACK
2. 数据库基础
2.1 操作数据库
操作数据库 ——> 操作数据库中的表 ——> 操作数据库中表的数据
MySQL关键字不区分大小写
以下关键字属于MySQL命令,并非SQL语句,只有在MySQL中才能使用
- 创建数据库
1 | CREATE DATABASE [IF NOT EXISTS] school; |
- 删除数据库
1 | DROP DATABASE [IF EXISTS] school; |
- 使用数据库
1 | -- 如果表名或者字符段名是一个特殊字符,需要带上 ` ` |
- 查看数据库
1 | SHOW DATABASES; -- 查看所有数据库 |
2.2 数据库的列类型
数值
- tinyint: 十分小的数据 , 1个字节
- smallint: 较小的数据 , 2个字节
- mediumint:中等大小的数据, 3个字节
- int: 标准的整数 , 4个字节
- bigint: 较大的数据 , 8个字节
- float: 浮点数, 4个字节
- double: 浮点数, 8个字节
- decimal: 字符串形式的浮点数, 金融计算时,一般使用decimal
其中浮点型数据创建时可以指定保留位数。例如double(M, D)代表小数点后保留D位小数,M代表整数部位+小数部位的总长度,如果超出范围,则插入临界值。M和D可以省略,如果是decimal,则默认M=10,D=0;如果是float和double,则会根据插入的数值的精度来决定精度。
字符串
- char: 固定大小字符串 0~255(在内存中分配固定长度存储数据)
- varchar: 可变长度字符串 0~65535,类比Java中String(按照实际字符串大小在内存中分配空间)
- tinytext: 微型文本 2^8 - 1
- text: 文本串 2 ^16 - 1 保存大文本
char(M)表示最大长度不能超过M,其中M可以省略,默认为1;varchar(M)的M不可以省略。
时间日期
类比java.util.Date
- date: YYYY-MM-DD 日期格式
- time : HH:mm::ss 时间格式
- datetime: YYYY-MM-DD HH:mm::ss 最常用的时间格式
- timestamp: 时间戳,1970.01.01到现在的毫秒数
- year: 年份表示
大对象
- BLOB:Binary Large Object 二进制大对象(存储图片、视频等流媒体信息)对应Java中的Object
- CLOB:Character Large Object 字符大对象(存储较大文本,比如可以存储4G的字符串)对应Java中的Object
NULL
- 没有值,未知
- 注意,不要使用NULL进行运算,NULL和任何数参与运算,计算结果都为NULL
- 想避免NULL对运算结果产生影响,则使用
IFNULL
空处理函数:IFNULL(XXX, 0)
2.3 存储引擎
存储引擎是对一张表而言,而非对数据库而言,不同的表可以有不同的存储引擎(可以在 CREATE 语句中指定存储引擎类型)
存储引擎即一张表在数据库中的存储方式。
MyISAM | InnoDB | |
---|---|---|
事务支持 | 不支持(最新版支持) | 支持 |
数据行表锁 | 不支持行锁,只支持表锁 | 支持行锁 |
外键约束 | 不支持 | 支持 |
全文索引 | 支持 | 不支持(最新版支持) |
表空间的大小 | 较小 | 较大,约为2倍 |
缓存 | 只缓存索引 | 不仅缓存索引,还要缓存真实数据 |
关注点 | 性能 | 事务 |
- MyISAM:可被转换为压缩、只读表来节省空间。节约空间,速度较快
- InnoDB:安全性高,支持事务的处理,多表多用户操作,在MySQL服务器崩溃后提供自动恢复,支持级联删除和更新
- MEMORY:查询速度最快,表数据和索引被存储在内存中,不支持事务,数据容易丢失。不能包含TEXT或BLOB字段
在物理空间存在的位置
所有数据库文件都存在data目录下,一个文件夹就是一个数据库,本质还是文件的存储。MySQL引擎在物理文件上的区别:
- InnoDB:
*.frm
表结构的定义文件*.ibd
数据和索引共同存储在一个文件中
- MyISAM:
*.frm
表结构的定义文件*.MYD
数据文件(data)*.MYI
索引文件(index)
其中,InnDB 采用聚集(聚数)索引(索引和真实数据存储在一起),MyISAM 采用非聚集(稀疏)索引(索引和真实数据分开存储),这才导致了二者在磁盘上存储文件类型的区别。
三种存储引擎的适用条件:
- MyISAM:适用于大量的数据读而少量数据更新的混合操作,另一种适用情形是使用压缩的只读表
- InnoDB:适用于查询中包含较多的数据更新操作,其行级锁机制和多版本的支持为数据读取和更新的混合操作提供了良好的并发机制
- MEMORY:适用于存储非永久需要的数据,或者是能够从基于磁盘的表中重新生成的数据
2.4 数据表的类型
设置数据库表的字符集编码
1 | CHARSET=utf8 |
不设置的话,会是MySQL默认的字符集编码Latin1(不支持中文)。
可以在my.ini中配置默认的编码:
1 | character-set-server=utf8 |
2.5 约束
在创建表时,可以给表的字段添加相应的约束,从而保证表中数据的合法性、有效性和完整性。
- 非空约束(NOT NULL):约束的字段不能为NULL
- 唯一约束(UNIQUE):约束的字段不能重复
- 主键约束(PRIMARY KEY):约束的字段既不能为NULL,也不能重复(简称PK)
- 外键约束(FOREIGN KEY):用于限制两个表的关系,约束的字段的值必须来自于主表的关联列的值(简称FK)
- 无符号约束(UNSIGNED):约束该字段为无符号
- 零填充约束(ZEROFILL):限制该字段0填充
- 自增约束(AUTO_INCREMENT):限制该字段自增
- 默认值约束(DEFAULT):设置该字段的默认值
唯一性约束(UNIQUE)
UNIQUE:唯一性约束修饰的字段具有唯一性,不能重复,但可以设置为NULL。
若给两列同时加UNIQUE,则为表级约束,联合起来不重复即可。若在列后面直接加UNIQUE,则为列级约束,该列数据不能有重复。
主键(PRIMARY KEY)
PRIMARY KEY:主键值是这行数据在这张表中的唯一标识(第一范式要求每张表必须有主键)。
一张表的主键约束只能有一个,可以是单一主键(推荐)或复合主键(不推荐,违反三范式)。主键本身也包含了唯一性约束。主键值最好是和业务没有关系的自然数。在字段后添加AUTO_INCREMENT设置主键值自增
外键(FOREIGN KEY)
FOREIGN KEY:用于减少表的冗余,将一张表拆成两张表。
外键引用的父表的字段必须是唯一的(UNIQUE),一般情况都是子表外键引用父表主键
无符号约束(UNSIGNED)
- 无符号的整数
- 声明了该列不能声明为负数
零填充约束(ZEROFILL)
- 0填充的
- 不足的位数,使用0填充。zerofill设置为3时,5——005
自增约束(AUTO_INCREMENT)
- 自动在上一条记录的基础上+1(默认)
- 通常用来设计唯一的主键,例如Index,必须是整数类型
- 可以自定义设置主键自增的起始值和步长
默认值约束(DEFAULT)
- 设置默认的值
- 例如若sex设置默认值为男,如果不指定该列的值,则会设置为默认值
增加外键约束的方式:
方式一:在创建表的时候,增加约束(比较麻烦)
1 | CREATE TABLE `grade`( |
删除有外键关系的表时,必须先删除引用别人的表(从表),再删除被引用的表(主表)
方式二:创建表成功后,添加外键约束
1 | CREATE TABLE `grade`( |
以上操作都是物理外键
,数据库级别的外键,不建议使用(避免数据库过多造成困扰)
- 数据库就是单纯的表,只用来存数据,只有行(数据)和列(字段)
- 想使用多张表的数据,想使用外键时,使用程序实现
主键约束(PRIMARY KEY)和唯一约束(UNIQUE)对比:
保证唯一性 | 是否允许为空 | 一个表可以有几个 | 是否允许组合 | |
---|---|---|---|---|
主键约束 | √ | × | 最多有一个 | √ |
唯一约束 | √ | √ | 可以有多个 | √ |
列级约束和表级约束对比
位置 | 支持的约束类型 | 是否可以起约束名 | |
---|---|---|---|
列级约束 | 列的后面 | 语法都支持,但外键没有效果 | 不可以 |
表级约束 | 所有列的下面 | 默认和非空约束不支持,其他支持 | 可以(主键没有效果) |
3. 数据库定义语言(DDL)
3.1 创建表(CREATE)
1 | -- 注意点 英文括号(),表的名称和字段尽量使用`` |
一般格式:
1 | CREATE TABLE [IF NOT EXISTS] `表名`( |
约束位置处可以添加:
UNSIGNED
:限制该字段为无符号ZEROFILL
:该字段0填充AUTO_INCREMENT
:该字段自增DEFAULT
:设置该字段的默认值NOT NULL
:限制该字段不能为NULLUNIQUE
:限制该字段不能重复PRIMARY KEY
:约束该字段既不能为NULL,也不能重复FOREIGN KEY
:约束该字段的值必须来自于主表的关联列的值(外键约束在列级约束处无效果)
常用命令:
1 | SHOW CREATE DATABASE `school`; -- 查看创建数据库的语句 |
复制表:
1 | -- 复制表的结构+数据 |
3.2 修改表(ALTER)
修改表的结构
1 | -- 修改表名:ALTER TABLE 旧表名 RENAME AS 新表名 |
删除表的结构
1 | -- 删除表的字段:ALTER TABLE 表名 DROP 字段名 |
注意点:
- 字段名使用``包裹
- 注释用-- 或/**/
- sql关键字大小写不敏感
3.3 删除表(DROP)
1 | DROP TABLE IF EXISTS t_student; |
4. 数据库操纵语言(DML)
4.1 添加数据(INSERT)
语法:INSERT INTO 表名(字段名1, 字段名2) VALUES (值11,值12), (值21,值22)
1 | -- 插入一个数据的一个字段时 |
注意事项:
- 字段和字段使用英文逗号隔开
- 表后括号内的字段可以省略,但是后面的值必须与字段名一一对应
- 可以同时插入多条数据,此时VALUES后面的值使用逗号隔开
VALUES(), (), ()...
4.2 修改数据(UPDATE)
语法:UPDATE 表名 SET 字段名1=value, [字段名2=value2, …] WHERE [条件]
条件:WHERE子句,运算符
1 | -- 修改学员名字,带了条件 |
操作符 | 含义 | 范围 | 结果 |
---|---|---|---|
= | 等于 | 5=6 | false |
<=> | 安全等于 | 5<=>NULL | false |
<> 或 != | 不等于 | 5<>6 | true |
>,>= | 大于,大于等于 | 5>6, 5>=6 | false |
<,<= | 小于,小于等于 | 5<6, 5<=6 | true |
BETWEEN … AND … | 闭区间[] | BETWEEN 2 AND 5 | [2, 5] |
AND | && | 5>1 AND 1>2 | false |
OR | || | 5>1 OR 1>2 | true |
注意:
- 字段名是数据库的列,尽量带上``
- 筛选的条件如果没有指定,则会修改所有的列
- value是一个具体的值,也可以是一个变量(时间变量)
- 多个设置的属性之间使用英文逗号隔开
- 安全等于<=>可以判断某字段数值是否等于NULL,而普通等于=无法判断是否为NULL
4.3 删除数据(DELETE)
语法:DELETE FROM 表名 [WHERE 条件]
作用:用于删除小表,删除后可以回滚,删除速度较慢
1 | -- 删除数据 (避免这样写,会全部删除) |
TRUNCATE 命令
作用:用于删除大表,表被截断,不可回滚,永久消失,删除速度较快。完全清空一个数据库表,表的结构和索引约束不会变。
1 | -- 清空表 |
TRUNCATE与DELETE区别
- 相同点:都能删除数据,都不会删除表结构
- 不同点:
- TRUNCATE:删除后不可回滚,永久丢失,删除速度较快。重新设置自增列,自增会归零,不会影响事务,其后不能加
WHERE
过滤 - DELETE:删除后可以回滚,删除速度较慢。不影响自增列,删除后自增不归零
- TRUNCATE:删除后不可回滚,永久丢失,删除速度较快。重新设置自增列,自增会归零,不会影响事务,其后不能加
1 | DELETE FROM `test`; -- 不会影响自增 |
了解即可: DELETE删除的问题
重启数据库时:
- InnoDB,自增列会从1开始(存在内存当中,断电即失)
- MyISAM,继续从上一个自增量开始(存在文件中,不会丢失)
5. 数据库查询语言(DQL)
5.1 DQL
DQL:Data Query Language,数据库查询语言
- 所有的查询操作都使用SELECT
- 数据库中最核心的语言
- 使用频率最高
SELECT完整语法:
1 | SELECT[ALL | DISTINCT | DISTINCTROW | TOP] |
SELECT语句执行顺序:
SELECT
在HAVING
之后执行,因此分组函数不能在WHERE
中使用
1 | SELECT -- 7 |
5.2 指定查询字段(SELECT)
语法:SELECT 字段,… FROM 表
有时列的名字不是那么见名知意,此时可以其别名,使用AS :字段名 AS 别名 或 表名 AS 别名
1 | -- 查询全部学生 SELECT 字段 FROM 表 |
去重 DISTINCT
作用:去除SELECT查询出来的结果中重复的数据,重复数据只显示一条
1 | -- 查询一下有哪些同学参加了考试 |
数据库的列(表达式)
1 | SELECT VERSION(); -- 查询系统版本(函数) |
数据库中的表达式:文本值,列,NULL,函数,计算表达式,系统变量…
语法:SELECT 表达式 FROM 表
5.3 条件子句(WHERE)
作用:检索数据中符合条件
的值
搜索的条件由一个或多个表达式组成,结果为布尔值
逻辑运算符
运算符 | 语法 | 描述 |
---|---|---|
AND && | a AND b a && b | 逻辑与,两个都为真,结果为真 |
OR || | a OR b a || b | 逻辑或,其中一个为真,结果为真 |
NOT ! | NOT a ! a | 逻辑非,真变假,假变真 |
1 | -- && AND |
模糊查询:比较运算符
运算符 | 语法 | 描述 |
---|---|---|
IS NULL | a IS NULL | 如果操作符a为NULL,结果为真 |
IS NOT NULL | a IS NOT NULL | 如果操作符a为NOT NULL,结果为真 |
BETWEEN | a BETWEEN b and c | 若a在b和c之间,结果为真 |
LIKE | a b | SQL匹配,如果a匹配b,结果为真 |
IN | a IN (a1, a2, a3…) | 假设a在a1,或者a2…其中的某一个值中,则结果为真 |
1 | -- ======================= 模糊查询 ========================= |
注意:使用LIKE
时,若想匹配下划线_,则使用转义的方式。
5.4 连接查询(JOIN )
-
按照年代分类:
- SQL92(旧的语法)
- SQL99(新的语法)
-
按照表的连接方式分类:
- 内连接(包括等值连接、非等值连接、自连接)
- 外连接(包括左外连接,右外连接)
- 全连接
笛卡尔积现象:当两张表进行连接查询时,没有任何条件进行限制,最终的查询结果条数是两张表记录条数的乘积。(例如第一张表有7条数据,第二张表有8条数据,则不加条件限制时,查询出56条数据)避免笛卡尔积现象的方法:使用ON关键字加条件进行过滤。但避免笛卡尔积现象并不会减少记录的匹配次数,数据库在匹配数据时仍然是搜索了56次,但是只滤过剩下了符合条件的数据
语法:FROM 要查询的表 INNER/RIGHT/LEFT JOIN 要连接的表 ON 交叉条件
MySQL 不支持 FULL OUTER JOIN
- INNER JOIN:内连接,又叫等值连接,只返回两个表中连接字段相等的行数据
- LEFT JOIN:左连接,从左表中返回所有值,即使右表中没有匹配(没匹配上的数据显示NULL)
- RIGHT JOIN:右连接,从右表中返回所有值,即使左表中没有匹配(没匹配上的数据显示NULL)
数据库在通过JOIN连接两张或多张表来返回记录时,都会生成一张中间的临时表,然后再将这张临时表返回给用户,或与下一层的表进行连接。
在使用LEFT JOIN 时,ON和WHERE条件的区别如下:
- ON条件是在生成临时表时使用的条件,它不管ON中的条件是否为真,都会返回左边表中的记录。(为真的部分返回数值,为假的部分返回NULL)
- WHERE条件是在临时表生成好后,再对临时表进行过滤的条件。
ON用于判断哪个字段在两个表中相等,从而连接两个表,WHERE则是在连接后生成的临时表的基础上进行过滤。
在使用INNER JOIN 时,ON和WHERE条件产生的效果相同。
1 | -- =========================== 连接查询 JOIN ========================== |
自连接
自己的表和自己的表连接,核心:一张表拆为两张一样的表即可
原表
categoryid | pid | categoryName |
---|---|---|
2 | 1 | 信息技术 |
3 | 1 | 软件开发 |
4 | 3 | 数据库 |
5 | 1 | 美术设计 |
6 | 3 | Web开发 |
7 | 5 | PS技术 |
8 | 2 | 办公信息 |
拆成父类表和子类表
父类
categoryid | categoryName |
---|---|
2 | 信息技术 |
3 | 软件开发 |
5 | 美术设计 |
子类
pid | categoryid | categoryName |
---|---|---|
3 | 4 | 数据库 |
2 | 8 | 办公信息 |
3 | 6 | Web开发 |
5 | 7 | PS技术 |
操作:查询父类对应的子类关系
父类 | 子类 |
---|---|
信息技术 | 办公信息 |
软件开发 | 数据库 |
软件开发 | Web开发 |
美术设计 | PS技术 |
1 | -- 查询父子信息: 把一张表选定为两张一样的表 |
5.5 分组和过滤(GROUP BY / HAVING)
语法:GROUP BY … HAVING …
GROUP BY
:按照某个字段或者某些字段进行分组,可以进行多个字段分组HAVING
:必须放在GROUP BY
后,对分组后的结果进行过滤
GROUP BY
语句在WHERE
语句之后执行。
若某个语句中没有显式GROUP BY,则编译时会在最后添加缺省的GROUP BY
1 | -- 查询不同课程的平均分,最高分,最低分,平均分大于80分 |
当一条SQL语句有GROUP BY时,只允许SELECT查询参加分组的字段和分组函数。
注意:在SELECT中使用到分组函数或使用GROUP BY关键字时,需要使用ANY_VALUE()修饰普通字段,其会选择被分到同一组的数据里第一条数据的指定列值作为返回数据,即只返回一个值。
因为分组函数或使用GROUP BY关键字在使用时会从多条匹配到的数据中取出某一个作为返回结果(例如MAX(),只取了最大的数据),此时,没有被分组函数修饰的字段将匹配到多条数据,因此必须从中选出某一条作为返回结果,ANY_VALUE()即从中返回了第一条数据。
为提高效率,可先使用WHERE进行筛选,再在其基础上使用GROUP BY分组。若先GROUP BY/HAVING,再WHERE,则效率低于前者。如果能在GROUP BY前使用WHERE过滤,则最好先使用WHERE。
5.6 排序(ORDER BY )
语法:ORDER BY 字段名 DESC/ASC
- 升序ASC
- 降序DESC
注意:当指定多个字段进行排序时,越靠前的字段越起主导地位,后面的字段只会在前面的字段排序相等的情况下才会启用。例如ORDER BY XXX DESC, YYY ASC
,先按照XXX进行降序排列,再在其基础上,对XXX相等的数据进行升序排列。
1 | -- ======================== 分页LIMIT 排序ORDER BY ========================= |
5.7 分页(LIMIT)
语法:LIMIT 查询起始值,pagesize
1 | -- 分页:缓解数据库压力,给人更好的体验 |
5.8 子查询
目标:WHERE(不使用固定值,而使用计算出来的值)
本质:在WHERE语句中嵌套一个子查询语句
1 | -- 查询数据库结构-1的所有考试结果(学号,科目编号,成绩),降序排列 |
注意:子查询的效率不如多表连接查询(JOIN)。因为MySQL会将子查询中最左边的表作为主表,首先遍历主表,然后再将每条数据传到子查询中与子查询做关联,因此子查询的时间复杂度为两个表的行数相乘 O(N * M)
,而多表连接查询的时间复杂度仅为两个表的行数相加。这样就导致了如果主表数据很多,子查询的效率会非常低。
5.9 合并查询结果(UNION)
语法:SELECT XXXXX UNION SELECT XXXXX
作用:用于将两个SELECT语句查询到的结果合并成一张表(行数据拼接起来),可以把两个不相干的表中的数据拼接起来显示(例如要查询的结果来自于多张表,并且这些表之间没有直接的连接关系)。
要求:
- 前后两个查询结果的列数必须一致
- 多条查询语句的查询的每一列的类型和顺序最好一致
UNION
关键字默认去重,如果使用UNION ALL
可以包含重复项
6. MySQL函数
6.1 普通函数
1 | -- 数学运算 |
使用 SELECT CONCAT(field1, ": ", field2) FROM table 可以将表中的两个字段拼接出来返回一个 String 对象
6.2 分组函数
分组函数都是对某一组数据进行操作的。 其也被称为聚合函数、多行处理函数:输入多行,最终输出一行结果。分组函数在GROUP BY
之后使用,用于统计分组后同一组内数据的最大/小值、平均值和总数。
- 分组函数自动忽略NULL,无需添加WHERE条件语句判断数据是否为NULL
- 分组函数不能直接使用在WHERE子句当中,因为WHERE语句在GROUP BY语句之前执行,而分组函数必须在分完组才能执行(即必须在GROUP BY语句之后执行)
- 分组函数一般都会和GROUP BY联合使用,其在GROUP BY语句执行后才执行
例如:SELECT ename, sal FROM emp WHERE sal > AVG(sal);
有语法错误,因为分组函数不能在WHERE子句中使用。修改办法:使用子查询语句SELECT ename, sal FROM emp WHERE sal > (SELECT AVG(sal) FROM emp);
函数名称 | 描述 |
---|---|
COUNT() | 计数 |
SUM() | 求和 |
AVG() | 平均值 |
MAX() | 最大值 |
MIN() | 最小值 |
1 | SELECT COUNT(`StudentName`) FROM student; -- COUNT(字段),指定查询某列,会忽略所有的NULL值 |
count(*)、count(1)、count(某索引) 和 count(某普通字段) 的区别:
count(*)
:和字段内容没关系,会查询所有包含NULL在内的行数(统计总记录条数)。底层默认是通过主键索引进行快速遍历所有页结构统计总数的,大多数情况下性能最高(如果表中不止一个索引,则会自动选择一个占用空间最小的二级索引来进行统计:因为占用空间小意味着加载的页就少,磁盘 IO 就少,性能显然更高)count(1)
:结果和count(*)
相同,也是统计总记录条数。底层也是默认通过主键索引进行快速遍历所有页结构统计总数的,性能和count(*)
相近(也会自动选择占用空间最小的二级索引)count(某索引)
:性能略低于前面二者(多了一步返回索引值),也是统计总记录条数。底层也是通过快速遍历索引树统计总数的。count(某普通字段)
:和字段内容有关系,会查询除了NULL值以外的行数(忽略NULL值),前面三者因为都是遍历索引,而索引不可能为空,因此会把整个表的所有记录给遍历到(即使有些记录是包含NULL的,但是因为索引不为NULL,所以统计出的结果就是全部数据)。因为普通字段没有索引,所以会进行全表扫描,性能低于前面三者
只要走索引进行遍历统计,肯定会统计出总记录数(这些记录中可能某个字段为NULL)。但是如果不走索引,全表扫描普通字段,则就会排除掉该字段为NULL的记录。
但是,当表中除了主键索引外还有其他普通二级索引,那么MySQL优化器就会使得 count(*)
和 count(1)
都不走主键索引,而是走占用空间最小(key_len
最小)的二级索引。这是因为普通二级索引所在的行结构大小较小(主键索引所在的行结构会把所有列信息保存到树中),同一个页结构中可以存储更多的行数据,因此遍历速度更快一些(因为与磁盘 IO 变少了)。
以上分析都是对于 InnoDB 存储引擎而言。若是 MyISAM,则因为其采用的是表级锁,因此可以维护一个 meta 信息,保存当前表中的行记录数 row_count
,数据一致性由表级锁来保证。因此其查询时间复杂度为 O(1)。而 InnoDB 因为需要支持事务,采用行级锁和 MVCC 机制,所以无法维护一个 row_count
(可能并发情况下数据不一致),因此每次必须全表扫描。
6.3 数据库级别的MD5加密(扩展)
MD5:Message-Digest Algorithm,信息摘要算法。
MD5主要增强算法复杂度和不可逆性。MD5破解网站的原理:背后有一个字典,一一查询匹配MD5加密后的值与加密前的值,看能否找到匹配。
1 | -- ====================== 测试MD5 加密 ====================== |
7. 事务
7.1 什么是事务
TCL: Transaction Control Language 事务控制语言。
事务(Transaction):事务由单独单元的一个或一组sql语句组成,在这个单元中,每个MySQL语句是相互依赖的,这个执行单元 要么全部执行,要么全部不执行
- SQL语句1正在执行:A给B转账200 A:1000 ——> 200 B:200
- SQL语句2正在执行:B收到A的钱 B:800 ——> B:400
将一组SQL放在一个批次中去执行。上述两条语句组成一组来执行,要么都成功,要么都失败,否则钱会凭空消失。
7.2 事务原则
事务原则:ACID原则——原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability);脏读,幻读,不可重复读
1. 原子性(Atomicity)
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都执行,要么都不执行,没有中间状态。对于事务在执行中发生错误,所有的操作都会被回滚,整个事务就像从没被执行过一样。
2. 一致性(Consistency)
事务必须使数据库从一个一致性状态变换到另一个一致性状态。保证数据库一致性是指当事务完成时,必须使所有数据都具有一致的状态。(例如A给B转账前后,数据库中二者余额之和相等,转账前为一个一致性状态,转账后也为一个一致性状态)。
3. 隔离性(Isolation)
隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他数据是隔离的,并发执行的各个事务之间不能互相干扰。
4. 持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。
其中,原子性和持久性靠undo和redo日志来实现,隔离性通过线程的控制来实现
7.3 事务隔离级别
对于同时运行的多个事务,当这些事务访问数据库中相同的数据时,如果没有采取必要的隔离机制,就会导致各种并发问题:
1. 脏读(读取未提交数据):
指一个事务读取了另一个事务未提交的数据。例如,两个事务T1和T2,T1读取了已经被T2更新但是还没有被提交的字段,之后,若T2回滚,T1读取到的内容就是临时且无效的。
2. 不可重复读(前后多次读取,数据内容不一致):
指在一个事务内读取表中的某一行数据,过段时间,该字段数据被另一事务修改,此时第一个事务再读时读取结果不同。例如,两个事务T1和T2,T1读取了一个字段,然后T2更新了该字段,之后,T1再此读取同一字段时,值就不同了。
3. 幻读(前后多次读取,数据总量不一致):
事务A在执行读取操作,需要两次统计数据的总量,前一次查询数据总量后,此时事务B执行了新增数据的操作并提交后,这个时候事务A读取的数据总量和之前统计的不一样,就像产生了幻觉一样,平白无故的多了几条数据。(类似班级里刚才看还是一个人,再看变成两个人,就像产生了幻觉)
不可重复读和幻读的区别:
(1) 不可重复读是读取了其他事务更改的数据,针对UPDATE操作
解决:使用行级锁,锁定该行,事务A多次读取操作完成后才释放该锁,这个时候才允许其他事务更改刚才的数据。
(2) 幻读是读取了其他事务新增的数据,针对INSERT和DELETE操作
解决:使用表级锁,锁定整张表,事务A多次读取数据总量之后才释放该锁,这个时候才允许其他事务新增数据。
MySQL的JDBC中,默认开启事务,此时每一句sql语句都会在一个单独的事务中执行,例如两次查询语句都会在不同的事务中执行,执行完该语句都会立刻提交。
MySQL支持4种事务隔离级别,默认级别为 REPEATABLE READ
- READ UNCOMMITTED(读未提交数据):允许事务读取未被其他事务提交的变更。脏读、不可重复读和幻读都可能出现
- READ COMMITTED(读已提交数据):只允许事务读取已经被其他事务提交的变更。可以避免脏读,但不可重复读和幻读仍然可能出现
- REPEATABLE READ(可重复读):确保事务可以多次从一个字段中读取相同的值,在这个事务持续期间,禁止其他事务对这个字段进行更新。此时即使其他事务修改某字段并COMMIT,本事务查询时仍是原先值。可以避免脏读和不可重复读,但幻读仍然可能出现,即其他事务若插入了新的行,本事务查询时也会多出这些行,导致看起来像幻觉一样,每次读取的数据总量不同
- SERIALIZABLE(串行化):确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入,更新和删除操作。所有并发问题都可以避免,但性能十分低下。在某个事务读取时,其他事务阻塞,无法对该表进行操作。该级别下无法进行并发
查看隔离级别:
1 | SELECT @@TX_ISOLATION --(8.0以前) |
设置当前MySQL连接的隔离级别:
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED
设置全局MySQL连接的隔离级别:
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED
7.4 undo 和 redo 日志
在数据库系统中,既有存放数据的文件,也有存放日志的文件。在内存中既有日志缓存 log buffer,也有磁盘文件 log file。MySQL中的日志文件,有这么两种与事务有关:undo日志与redo日志。
7.4.1 undo 日志
undo日志的原理:为了满足事务的原子性,在操作任何数据之前,首先将数据备份到 undo log。然后进行数据的修改。如果出现了错误或者用户执行了 ROLLBACK 语句,系统可以利用 undo log 中的备份将数据恢复到事务开始之前的状态。并且数据库写入数据到磁盘之前,会把数据先缓存在内存中,事务提交时才会写入磁盘中。
用undo日志实现原子性的简化过程
假设有A、B两个数据,值分别为1,2。
- A. 事务开始.
- B. 记录A=1到 undo log.
- C. 修改A=3.
- D. 记录B=2到 undo log.
- E. 修改B=4.
- F. 将 undo log 写到磁盘。
- G. 将数据写到磁盘。
- H. 事务提交
1. 如何保证持久性?
事务提交前,会把修改后的数据保存到磁盘,也就是说只要事务提交了,数据肯定持久化了。
2. 如何保证原子性?
- 每次对数据库修改,都会把修改前数据记录在 undo log,那么需要回滚时,可以读取 undo log,恢复数据。
- 若系统在G和H之间崩溃,此时事务并未提交,需要回滚。而 undo log 已经被持久化,可以根据 undo log 来恢复数据
- 若系统在G之前崩溃,此时数据并未持久化到硬盘,依然保持在事务之前的状态
缺陷:每个事务提交前将数据和 undo log 写入磁盘,这样会导致大量的磁盘IO,因此性能很低。
如果能够将数据缓存一段时间,就能减少IO提高性能。但是这样就会丧失事务的持久性。因此引入了另外一种机制来实现持久化,即 redo log。
7.4.2 redo日志
和 undo log 相反,redo log 记录的是新数据的备份。在事务提交前,只要将 redo log 持久化即可,不需要将数据库中的数据持久化,减少了IO的次数。
undo + redo 事务的简化过程
假设有A、B两个数据,值分别为1,2
- A. 事务开始.
- B. 记录 A=1 到 undo log buffer.
- C. 修改 A=3.
- D. 记录 A=3 到 redo log buffer.
- E. 记录 B=2 到 undo log buffer
- F. 修改 B=4.
- G. 记录 B=4 到 redo log buffer.
- H. 将 undo log 写入磁盘
- I. 将 redo log 写入磁盘
- J. 事务提交
1. 如何保证原子性?
如果在事务提交前故障,通过 undo log 日志恢复数据。如果 undo log 都还没写入,那么数据就尚未持久化,无需回滚
2. 如何保证持久化?
注意,这里并没有出现数据的持久化。因为数据已经写入 redo log,而 redo log 持久化到了硬盘,因此只要到了步骤I
以后,事务是可以提交的。
3. 内存中的数据库数据何时持久化到磁盘?
因为 redo log 已经持久化,因此数据库数据写入磁盘与否影响不大,不过为了避免出现脏数据(内存中与磁盘不一致),事务提交后也会将内存数据刷入磁盘(也可以按照固设定的频率刷新内存数据到磁盘中)。
4. redo log 何时写入磁盘?
redo log 会在事务提交之前,或者 redo log buffer满了的时候写入磁盘
这里存在两个问题:
问题1:之前是写 undo 和数据库数据到硬盘,现在是写 undo 和 redo 到磁盘,似乎没有减少IO次数
- 数据库数据写入是随机IO,性能很差
- redo log 在初始化时会开辟一段连续的空间,写入是顺序IO,性能很好
- 实际上 undo log 并不是直接写入磁盘,而是先写入到 redo log buffer 中,当 redo log 持久化时,undo log 就同时持久化到硬盘了。
因此事务提交前,只需要对 redo log 持久化即可。另外,redo log 并不是写入一次就持久化一次, redo log 在内存中也有自己的缓冲池:redo log buffer
。每次写 redo log 都是写入到 buffer,在提交时一次性持久化到磁盘,减少IO次数。
问题2:redo log 数据是写入内存buffer中,当buffer满或者事务提交时,将 buffer 数据写入磁盘。redo log 中记录的数据,有可能包含尚未提交事务,如果此时数据库崩溃,那么如何完成数据恢复?
数据恢复有两种策略:
- 恢复时,只重做已经提交了的事务
- 恢复时,重做所有事务包括未提交的事务和回滚了的事务。然后通过 undo log 回滚那些未提交的事务
Innodb 引擎采用的是第二种方案,因此 undo log 要在 redo log 前持久化
总结:
- undo log 记录更新前的数据,用于保证事务原子性
- redo log 记录更新后的数据,用于保证事务的持久性
- redo log 有自己的内存buffer,先写入到 buffer,事务提交时写入磁盘
- redo log 持久化之后,意味着事务是可提交的
7.5 悲观锁和乐观锁
参考链接:https://cloud.tencent.com/developer/article/1450773
悲观锁
正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处 于锁定状态。
悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机 制,也无法保证外部系统不会修改数据)。
在悲观锁的情况下,为了保证事务的隔离性,就需要一致性锁定读。读取数据时给加锁,其它事务无法修改这些数据。修改删除数据时也要加锁,其它事务无法读取这些数据。
乐观锁
相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。
而乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本( Version )记录机制实现。
何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。
此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如 果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
7.6 执行事务顺序
- 关闭自动提交:
SET autocommit = 0
- 开启一个事务:
START TRANSACTION
- 提交(执行成功):
COMMIT
,获得新的数据库 - 回滚(执行失败):
ROLLBACK
,返回原先数据库
- 提交(执行成功):
- 开启自动提交:
SET autocommit = 1
1 | -- MySQL是默认开启事务自动提交的 |
测试案例:
1 | -- 模拟转账 |
7.7 DELETE 和 TRUNCATE 在事务中的区别
DELETE
在事务提交前使用,若回滚,则数据会恢复TRUNCATE
事务提交前使用,若回滚,则数据依旧不会恢复
8. 视图
视图(VIEW):一种虚拟存在的表。站在不同的角度去看待同一份数据
视图是一种虚拟存在的表,行和列的数据来自定义视图的查询中使用的表,并且是在使用视图时动态生成的,只保存了SQL逻辑,不保存查询结果。视图使用时可以像操纵表一样操纵表中的数据。
8.1 操作视图
创建视图
语法:CREATE VIEW 视图名 AS DQL语句(SELECT … FROM …)
1 | CREATE VIEW emp_view |
删除视图
语法:DROP VIEW 视图名
1 | DROP VIEW emp_view; |
修改视图
语法:ALTER VIEW 视图名
1 | CREATE OR REPLACE VIEW emp_view -- 如果存在则替换 |
更新视图
语法同操作表时一模一样
1 | UPDATE emp_view SET sal = 1000 WHERE dname = 'ACCOUNTING'; |
视图的更新性和视图中查询的定义有关系,以下类型的视图是不能更新的:
- 包含以下关键字的SQL语句:分组函数、
DISTINCT
、GROUP BY
、HAVING
、UNION
或UNION ALL
- 常量视图
SELECT
中包含子查询JOIN
FROM
一个不能更新的视图WHERE
子句的子查询引用了FROM
子句中的表
8.2 视图的作用
视图的特点:通过对视图的操作会影响到原表数据。可以面向视图对象进行增删改查,对视图对象的增删改查将会导致原表被操作。
视图的作用:假设有一条非常复杂的SQL语句,而这条SQL语句需要在不同的位置上反复使用。每次使用这条SQL语句的时候都需要重新编写(很麻烦),这时可以把这条复杂的SQL语句以视图对象的形式新建。在需要编写这条SQL语句的位置直接使用视图对象,可以大大简化开发,并且有利于后期的维护,因为修改时只需要修改一个视图对象所映射的SQL语句。
8.3 视图和表的区别
创建语法 | 是否占用实际物理空间 | 使用 | |
---|---|---|---|
视图 | CREATE VIEW |
只是保存了SQL逻辑 | 主要用来查询 |
表 | CREATE TABLE |
保存了实际数据 | 增删改查 |
9. 变量
MySQL中变量类型:
- 系统变量:
- 全局变量:针对数据库全局有效。服务器每次启动将为所有的全局变量赋初值,针对所有的会话(连接)有效,但是不能跨重启(数据库重启后之前设置的值会恢复)
- 会话变量:仅针对某次会话(连接)有效
- 自定义变量:
- 用户变量:在当前会话中有效,同与会话变量的作用域
- 局部变量:仅仅在定义它的
BEGIN END
中有效
作用域 | 定义和使用的地方 | 语法 | |
---|---|---|---|
用户变量 | 当前会话 | 会话中的任何地方 | SET @a = 0; 必须加@符号,不用限定类型 |
局部变量 | BEGIN END 中 |
BEGIN END 中,且为第一句话 |
DECLARE a INT DEFAULT 0; 一般不用加@符号,需要限定类型 |
10. 存储过程和函数
存储过程:一组预编译好的SQL语句的集合,可以理解成批处理语句(类似于Java中的函数)。
- 提高代码的重用性
- 简化操作
- 减少了编译次数并且减少了和数据库服务器的连接次数,提高了效率
10.1 存储过程的语法
创建存储过程
1 | CREATE PROCEDURE 存储过程名(参数列表) |
参数列表包含三部分:参数模式、参数名和参数类型。其中参数模式包含:
IN
:该参数可以作为输入,也就是该参数需要调用方传入值OUT
:该参数可以作为输出,也就是该参数可以作为返回值INOUT
:该参数既可以作为输入又可以作为输出,也就是该参数既需要传入值,又可以返回值
如果存储过程体仅仅只有一句话,则BEGIN END
可以省略。存储过程体中的每句SQL语句的结尾要求必须加分号。存储过程的结尾可以使用DELIMITER
重新设置。
调用存储方法
1 | CALL 存储过程名(实参列表); |
删除存储过程
1 | DROP PROCEDURE 存储过程名; |
查看存储过程的信息
1 | SHOW CREATE PROCEDURE 存储过程名; |
10.2 创建和调用存储过程
空参列表
1 | SELECT * FROM `admin`; |
带IN模式参数的存储过程
1 | CREATE PROCEDURE myp2(IN `name` VARCHAR(20)) |
1 | CREATE PROCEDURE myp3(IN `name` VARCHAR(20), IN `password` VARCHAR(20)) |
带OUT模式的存储过程
1 | CREATE PROCEDURE myp4(IN beautyName VARCHAR(20), OUT boyName VARCHAR(20)) |
带INOUT模式的存储过程
1 | CREATE PROCEDURE myp5(INOUT a INT, INOUT b INT) |
10.3 函数的语法
存储过程和函数的区别:
- 存储过程:可以有0个返回,也可以有多个返回,适合做批量插入、批量更新
- 函数:有且只有一个返回,适合做处理数据后返回一个结果
创建函数
1 | CREATE FUNCTION 函数名(参数列表) RETURNS 返回类型 |
- 参数列表包含:参数名、参数类型。
- 函数体:肯定会有
RETURN
语句。如果RETURN
语句没有放在函数体的最后也不会报错,但不建议 - 当函数体中只有一句话,可以省略
BEGIN END
- 使用
DELIMITER
语句设置结束标记
调用函数
1 | SELECT 函数名(列表参数) |
查看函数
1 | SHOW CREATE FUNCTION myf1; |
删除函数
1 | DROP FUNCTION myf1; |
10.4 创建和调用函数
无参数,有返回
1 | CREATE FUNCTION myf1() RETURNS INT |
有参数,有返回
1 | CREATE FUNCTION myf2(empName VARCHAR(20)) RETURNS DOUBLE |
10.5 应用场景:批量插入数据脚本
环境准备
1、建表SQL。
1 | /* 1.dept表 */ |
2、由于开启过慢查询日志,开启了
bin-log
,我们就必须为function
指定一个参数,否则使用函数会报错。
1 | 在mysql中设置 |
上述修改方式MySQL重启后会失败,在my.cnf
配置文件下修改永久有效。
1 | [mysqld] |
创建函数
1 | # 1、函数:随机产生字符串 |
创建存储过程
1 | # 1、函数:向dept表批量插入 |
调用存储过程
1 | # 1、调用存储过程向dept表插入10个部门。 |
11. 权限管理和备份
11.1 权限管理
SQL命令
用户表:mysql.user(存储用户信息)
本质:修改权限,添加用户等操作本质上是对这张表进行增删改查
1 | -- 创建用户: CREATE USER 用户名 IDENTIFIED BY '密码' |
11.2 MySQL备份
为什么要备份:
- 保证重要的数据不丢失
- 数据转移
MySQL数据库备份的方式:
- 直接拷贝物理文件,data目录
- 在Navicat工具中手动导出
- 使用命令行:mysqldump
1 | # ================================= 导出 =============================== |
12. 数据库设计的三大范式
12.1 为什么需要设计
当数据库交复杂时,就需要设计
糟糕的数据库设计:
- 数据冗余,浪费空间
- 数据库插入和删除都会麻烦/异常(屏蔽使用物理外键)
- 程序的性能差
良好的数据库设计:
- 节省内存空间
- 保证数据库的完整性
- 方便开发系统
软件开发中,关于数据库的设计:
- 分析需求,分析业务和需要处理的数据库需求
- 概要设计:设计关系图 E-R图
设计数据库的步骤:(个人博客)
- 收集信息,分析需求
- 用户表(用户登录注销,用户的个人信息,写博客,创建分类)
- 分类表(文章分类,谁创建的)
- 文章表(文章的信息)
- 评论表(评论的信息)
- 友链表(友链信息)
- 自定义表(系统信息,某个关键的字,或者一些主字段) 表中两列:key | value
- 标识实体(把需求落地到每个字段)
- 标识实体之间的关系
- 写博客:user --> blog
- 创建分类:user --> category
- 关注:user --> user
- 友链:links
- 评论:user --> user --> blog
12.2 三大范式
三大范式:设计表的依据,按照这个三范式设计的表不会出现数据冗余 https://www.bilibili.com/video/BV1fx411X7BD?p=74
1. 第一范式(1NF):原子性
任何一张表都应该有主键,并且每一个字段原子性不可再分,即每一列的信息不可再分
2. 第二范式(2NF):不可部分依赖
前提:必须满足第一范式。所有非主键字段完全依赖主键,不能产生部分依赖。即每一列都只和主键相关,而不能只与主键的一部分相关(主要针对联合主键而言),例如,某表中有联合主键,某些非主键字段只依赖联合主键中的其中一个,不依赖于另一个,就产生了冗余
多对多,三张表,关系表两个外键。若遇到不符合第二范式的表时,将一张多对多关系的表拆成三张表:
- 两个表分别存储独立的信息,每个表有一个主键
- 一张关系表,关系表里有两个外键,分别引用两个独立表
3. 第三范式(3NF):不可传递依赖
前提:必须满足第一范式和第二范式。所有非主键字段直接依赖主键,不能产生传递依赖。
一对多,两张表,多的表加外键。若遇到不符合第二范式的表时,将一张一对多关系的表拆成两张表:
- 一张内容少的表,存储会产生传递依赖的信息
- 另一张内容多的表加外键,引用第一张表
规范性和性能的问题:在实际的开发中,以满足客户的需求位置,有时候会拿冗余换执行速度
- 考虑商业化的需求和目标,数据库的性能更加重要
- 在规范性能问题的时候,需要适当考虑一下规范性
- 故意给某些表增加一些冗余的字段(从多表查询边单表查询)
- 故意增加一些计算列(从大数据量降低为小数据量的查询:索引)