为什么需要数据库?.
有人会说为什么需要用数据库去存数据,用文件存储不香吗?
的确文件存储数据是很方便的,但是当数据量比较大的情况下,如何管理这些数据就成了大问题
- 读写文件并解析出数据需要大量重复代码
- 从成千上万的数据中快速查询出指定数据需要复杂的逻辑。
如果每个应用程序都各自写自己的读写数据的代码,一方面效率低,容易出错,另一方面,每个应用程序访问数据的接口都不相同,数据难以复用。
所以,数据库作为一种专门管理数据的软件就出现了。应用程序不需要自己管理数据,而是通过数据库软件提供的接口来读写数据。至于数据本身如何存储到文件,那是数据库软件的事情,应用程序自己并不关心
┌──────────────┐
│ application │
└──────────────┘
▲│
││
read││write
││
│▼
┌──────────────┐
│ database │
└──────────────┘
数据模型.
数据库按照数据结构来组织、存储和管理数据,实际上,数据库一共有三种模型:
- 层次模型
- 网状模型
- 关系模型
层次模型就是以“上下级”的层次关系来组织数据的一种方式,层次模型的数据结构看起来就像一颗树:
┌─────┐
│ │
└─────┘
│
┌───────┴───────┐
│ │
┌─────┐ ┌─────┐
│ │ │ │
└─────┘ └─────┘
│ │
┌───┴───┐ ┌───┴───┐
│ │ │ │
┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐
│ │ │ │ │ │ │ │
└─────┘ └─────┘ └─────┘ └─────┘
网状模型把每个数据节点和其他很多节点都连接起来,它的数据结构看起来就像很多城市之间的路网:
┌─────┐ ┌─────┐
┌─│ │──────│ │──┐
│ └─────┘ └─────┘ │
│ │ │ │
│ └──────┬─────┘ │
│ │ │
┌─────┐ ┌─────┐ ┌─────┐
│ │─────│ │─────│ │
└─────┘ └─────┘ └─────┘
│ │ │
│ ┌─────┴─────┐ │
│ │ │ │
│ ┌─────┐ ┌─────┐ │
└──│ │─────│ │──┘
└─────┘ └─────┘
关系模型把数据看作是一个二维表格,任何数据都可以通过行号+列号来唯一确定,它的数据模型看起来就是一个Excel表:
┌─────┬─────┬─────┬─────┬─────┐
│ │ │ │ │ │
├─────┼─────┼─────┼─────┼─────┤
│ │ │ │ │ │
├─────┼─────┼─────┼─────┼─────┤
│ │ │ │ │ │
├─────┼─────┼─────┼─────┼─────┤
│ │ │ │ │ │
└─────┴─────┴─────┴─────┴─────┘
选取主键的一个基本原则是:不使用任何业务相关的字段作为主键。
因此,身份证号、手机号、邮箱地址这些看上去可以唯一的字段,均不可用作主键。
主键一般选用:
- 自增整数类型(auto_increment):数据库会在插入数据时自动为每一条记录分配一个自增整数,这样我们就完全不用担心主键重复,也不用自己预先生成主键;
- 全局唯一GUID类型:使用一种全局唯一的字符串作为主键,类似
8f55d96b-8acc-4636-8cb8-76bf8abc2f57
。GUID算法通过网卡MAC地址、时间戳和随机数保证任意计算机在任意时间生成的字符串都是不同的,大部分编程语言都内置了GUID算法,可以自己预算出主键。
数据库基本操作.
创建数据库(DDL).
create database 数据库名称;
-- 1
create database studentdb;
-- 2 为了避免用户自定义的名称与系统命令冲突,最好使用反引号(` `)包裹数据库名称、字段名称和数据表名称
create database `studentdb`;
-- 3 如果库已存在 可使用 if not exists
create database if not exists `studentdb`;
查看数据库(DDL).
-- 在完成创建数据库后,若要查看该数据库的信息
show create database 数据库名称;
-- 查看MySQL数据库服务器中已经存在的数据库
show databases;
选择数据库(DDL).
use 数据库名称;
use `studentdb`;
删除数据库(DDL).
-- 数据库的删除操作不仅会删除里面的数据,还会回收原来分配的存储空间。
drop database 数据库名称;
-- 1
drop database `studentdb`;
-- 2 若待删除数据库不存在 可使用 if exists
drop database if exists `studb`;
数据类型.
数值类型
整型
- tinyint
- smallint
- mediumint
- int
- bigint
浮点型
- float
- double
- decimal
布尔类型
字符串类型
日期与时间类型
数值 – 整数类型
数据类型 | 字节数 | 取值范围 | 说明 |
---|---|---|---|
TINYINT | 1 | 有符号:-128~127 无符号:0~255 |
最小的整数 |
SMALLINT | 2 | 有符号:-32 768 无符号:0 |
小型整数 |
MEDIUMINT | 3 | 有符号:-8 388 608 无符号:0 |
中型整数 |
INT | 4 | 有符号:-2 147 483 648 无符号:0 |
常规整数 |
BIGINT | 8 | 有符号:-9 223 372 036 854 775 808 无符号:0 |
较大的整数 |
数值 – 浮点类型
数据类型 | 字节数 | 取值范围 | 说明 |
---|---|---|---|
FLOAT | 4 | 有符号:-3.402 823 466E+38 无符号:0/1.175 494 351E-38 |
单精度 |
DOUBLE | 8 | 有符号:-1.797 693 134 862 315 7E+308~2.225 073 858 507 201 4E-308 无符号:0/2.225 073 858 507 201 4E-308 ~1.797 693 134 862 315 7E+308 |
双精度 |
DECIMAL(M,D) | M+2 | 有符号:-1.797 693 134 862 315 7E+308~2.225 073 858 507 201 4E-308 无符号:0/2.225 073 858 507 201 4E-308 ~1.797 693 134 862 315 7E+308 |
定点数 |
M : 数据长度 D : 小数点后的长度
字符串类型
数据类型 | 取值范围 | 说明 |
---|---|---|
CHAR | 0~2^8-1(字符) | 用于表示固定长度的字符串 |
VARCHAR | 0~2^8-1(字符) | 用于表示可变长度的字符串 |
ENUM | 0~2^16-1(字节数) | 表示枚举类型,只能存储一个枚举字符串值 |
BLOB | 0~2^16-1(字节数) | 用于表示二进制大数据(常规的) |
TEXT | 0~2^16-1(字节数) | 用于表示大文本数据(常规的) |
TINYBLOB | 0~2^8-1(字节数) | 用于表示二进制大数据(较小的) |
TINYTEXT | 0~2^8-1(字节数) | 用于表示大文本数据(较小的) |
MEDIUMBLOB | 0~2^24-1(字节数) | 用于表示二进制大数据(中等的) |
MEDIUMTEXT | 0~2^24-1(字节数) | 用于表示大文本数据(中等的) |
LONGBLOB | 0~2^32-1(字节数) | 用于表示二进制大数据(较大的) |
LONGTEXT | 0~2^32-1(字节数) | 用于表示大文本数据(较大的) |
BLOB和TEXT都是用于存储大量数据的,但二者的区别在于,对于存储的数据进行排序和比较时,BLOB是区分大小写的,而TEXT是不区分大小写的。
日期与时间类型
数据类型 | 功能说明 |
---|---|
DATA | 用于存储日期,存储格式为YYYY-MM-DD 例如:2008-12-24 |
TIME | 用于存储时间,存储格式为HH:MM:SS 例如:14:25:10 |
DATETIME | 用于存储日期和时间,存储格式为YYYY-MM-DD HH:MM:SS |
TIMESTAMP | 用于存储时间戳,存储格式为YYYYMMDDHHMMSS |
YEAR(M) | 用于存储年份,M用于指定年份的长度,其值为2或4中的一种 |
存储引擎.
InnoDB 存储引擎
- 默认的存储引擎
- 用于完成事务、回滚、崩溃修复和多版本并发控制的事务安全处理
- 提供了良好的事务管理、崩溃修复能力和并发控制
MyISAM 存储引擎
MyISAM 存储引擎是基于 ISAM 存储引擎发展起来的,它不仅解决了 ISAM的很多不足,还增加了很多有用的扩展。
其中,对于使用 MyISAM 存储引擎的数据表,会被存储成3个文件,文件名与表名相同,文件扩展名分别为 frm、myd 和 myi。扩展名 功能说明 Frm 用于存储表的结构 Myd 用于存储数据,是MYData的缩写 Myi 用于存储索引,是MYIndex的缩写 相比 InnoDB ,MyISAM 的优点是处理速度快;缺点是不支持事务处理等
MEMORY 存储引擎
MEMORY存储引擎,是MySQL中的一类特殊的存储引擎。在MEMORY存储引擎的表中,所有数据都保存在内存中,因此数据的处理速度快,但不能持久保存(程序出错或关机时会丢失数据),而且不能存储太大的数据。对于需要很快的读写速度,但数据量小、不需要持久保存的数据来说,MEMORY存储引擎是一个理想的选择。
ARCHIVE 存储引擎
ARCHIVE 存储引擎适合保存数量庞大、长期维护但很少被访问的数据。对于使用 ARCHIVE 存储引擎的数据表,数据存储时会利用 zlib 压缩库进行压缩,在记录被请求时会实时进行解压。需要注意的是,ARCHIVE 存储引擎仅仅支持查询和插入操作,且由于不支持数据索引,查询效率比较低。
查看存储引擎(DDL).
show engines;
数据表操作.
创建数据表(DDL).
create [temporary] table [if not exists] 数据表名
[(
col_name type [完整性约束条件],
...
)][table_options][select_statement];
[]中的内容是可选项
完整性约束条件 : 字段的某些特殊约束条件
参数名称 功能说明 TEMPORARY 如果使用该关键字,表示创建一个临时表 IF NOT EXISTS 如果表已经存在则不执行创建操作 col_name 字段名 Type 字段类型 table_options 表的一些特性参数 select_statement 用于根据SELECT语句的查询结果创建表
comment 表示 注释内容
注意.
数据名的最大长度64字节
表名的最大长度64字节
列名的最大长度64字节
索引名的最大长度64字节
别名的最大长度255字节
查看数据库中的表(DDL).
-- 前提 use 数据库
show tables;
-- 无需 use
show tables from 数据库名称;
查看表结构(DDL).
三种方案
-- 1 此种方式可以查看数据表的创建语句和表的字符编码
show create table `stu` \G
# \G 用于显示结果纵向排列
-- 2 此种方式用于查看表的字段信息
describe `stu`;
# 简写
desc `stu`;
# 可指定查看某一列
desc `stu` `sno`;
-- 3
# show [full] columns from 数据表名 [from 数据库名]
# show [full] columns from 数据库名.数据表名
show columns from `stu`
修改表结构(DDL).
alter [ignore] table 数据表名
add [column] create_definition [first | after column_name] # 添加新字段
| change [column] old_col_name new_col_name type #修改字段名称及类型
| modify [column] create_definition # 修改子句定义字段
| drop [column] col_name # 删除字段
| rename [as] new_tbl_name # 更改表名
-- ------------------------------
# create_definition : 包含 col_name type
# type : 数据类型,约束条件
在上述语法中, 当指定 IGNORE 时,如果出现重复关键的行,则只执行一行,其他重复的行被删除。其中,ALTER TABLE 语句允许指定多个动作,动作间使用英文逗号(,)分隔,每个动作表示对表的一个修改。
添加新字段.
修改字段名及类型.
需要注意的是,在使用“CHANGE”时,必须为新字段名称设置数据类型,即使与原来的数据类型相同,也必须进行重新设置。此外,当修改后的数据类型无法容纳原有数据时,修改将会失败。
修改字段数据类型.
删除字段.
.
更改表名称.
重命名表 (多个数据表).
rename table 源数据表名 to 新数据表名
# 该语句可以同时对多个数据表进行重命名,多个表之间以逗号“,”分隔
删除表(DDL).
drop table [if exists] 数据表名;
在开发时,应谨慎使用数据表删除操作,因为数据表一旦删除,表中的所有数据都将被清除
演示: 略
数据约束.
在 MySQL 中,为了减少输入错误和保证数据的完整性,可以对字段设置约束。所谓约束就是一种命名规则和机制,通过对数据的增、删、改操作进行一些限制,以保证数据库中数据的完整性。
常见的表约束(5种).
- 主键约束
- 非空约束
- 默认约束
- 唯一键约束
- 外键约束
MySQL 提供了两种定义约束的方式:
列约束和表约束。列约束定义在一个列上,只能对该列起约束作用
表约束一般定义在一个表的多个列上,要求被约束的列满足一定的关系。
非空约束 not null.
# 被约束的当前字段的值不能为空值 NULL
字段名 数据类型 NOT NULL;
# 如
sno varchar(12) not null
在 MySQL 中,所有数据类型的值都可以是 NULL,包括 INT、FLOAT 等数据类型。
需要注意的是,空字符串和 0 皆不属于空值 NULL。
唯一约束 unique.
# 唯一约束用于保证数据表中字段的唯一性,即表中字段的值不能重复出现
# 列级约束
字段名 数据类型 UNIQUE
# 表级约束
UNIQUE(字段名1, 字段名2, ……);
# 虽然不允许出现重复的值,但是可以出现多个空值 NULL
主键约束 primary key.
# 主键约束相当于唯一约束和非空约束的组合
# 每个表最多只允许含有一个主键,建立主键约束可以在列级别创建,也可以在表级别上创建
#主键约束可以由一个字段构成单字段主键,也可以由多个字段组合而成
# 列级约束
字段名 数据类型 PRIMARY KEY
# 表级约束
PRIMARY KEY (字段名1,字段名2,……)
默认约束 default.
# 默认约束用于指定数据表中字段的默认值
字段名 数据类型 DEFAULT 默认值;
外键约束 foreign key.
# 外键并不是通过列名实现的,而是通过定义外键约束实现的
FOREIGN KEY (外键) REFERENCES 其他表(其他表主键);
数据的操作.
插入记录(DML).
# 字段列表的字段 位置顺序可以随意更改的
insert into 表名(字段1,字段2,...) values(值1,值2,...);
# 省略字段列表 数据必须按照数据表定义字段时的顺序
insert into 表名 values(值1,值2,...);
# 指定字段
insert into 表名(字段1,字段2) values(值1,值2);
insert into 表名 set 字段1=值1,...;
# 同时添加多条记录
insert into 表名(字段1,字段2,...) values
(值1,值2,...),
(值1,值2,...),
...
(值1,值2,...);
修改记录(DML).
update 表名 set 字段1=值1,...[where 条件表达式];
# “WHERE条件表达式”是可选的,用于指定哪些记录需要被更新。否则,数据表中的所有记录都将被更新
删除记录(DML).
delete from 表名 [where 条件表达式];
# “表名”指的是待执行删除操作的表,WHERE 子句为可选参数,用于指定删除的条件,满足条件的记录才会被删除
# 若没有 条件表达式 代表 清空该表 除上述表达之外
# 清空表
truncate [table] 表名;
truncate 与 delete的区别.
1.DELETE语句是DML语句,TRUNCATE语句通常被认为是DDL语句。
2.DELETE语句后面可以跟WHERE子句,通过指定WHERE子句中的条件表达式只删除满足条件的部分记录,而TRUNCATE语句只能用于清空表中的所有记录。
3.使用TRUNCATE语句删除表中的数据后,再次向表中添加记录时,自动增加字段(auto_increment)的默认初始值重新由1开始,而使用DELETE语句删除表中的记录时,不影响自动增长值。
查询语句(DQL).
# 基本语法
SELECT [DISTINCT] *|{字段名1, 字段名2, 字段名3,……}
FROM 表名
[WHERE 条件表达式1]
[GROUP BY 字段名 [HAVING 条件表达式2]]
[ORDER BY 字段名 [ASC|DESC]]
[LIMIT [OFFSET] 记录数]
# AS子句 添加别名
SELECT 字段名称 [AS] 别名, 字段名2 [AS] 别名,... FROM 表名;
# 若别名是纯数字,或含有空格等特殊字符时,必须使用反引号“`”包裹。且在为字段定义别名时,AS可以省略使用空格代替。
组成部分 | 功能说明 |
---|---|
DISTINCT | 可选参数,用于剔除查询结果中重复的数据 |
* | “*”是通配符,表示表中所有字段 |
{字段名1,……} | 指定查询列表,与“*”为互斥关系,两者任选其一 |
FROM | 用于指定待查询的数据表 |
WHERE | 可选参数,用于指定查询条件 |
GROUP BY | 可选参数,用于将查询结果按照指定字段进行分组,“HAVING”也是可选参数,用于对分组后的结果进行过滤 |
ORDER BY | 可选参数,用于将查询结果按照指定字段进行排序。ASC表示升序,DESC表示降序 |
LIMIT | 是可选参数,用于限制查询结果的数量。第1个参数表示偏移量,第2个参数设置返回查询记录的条数 |
单表查询(DQL).
条件查询.
注意 mysql在查询时 不区分英文大小写.
多条件查询.
select * from 表名 where (字段1,字段2) = (值1,值2);
# or
select * from 表名
where 字段1 = 值1
and 字段2 = 值2;
比较大小.
= > < >= <= <> != !> !<
带IN关键字查询.
# IN 关键字,用于判断某个字段的值是否在指定集合中
SELECT *|{字段名1, 字段名2, 字段名3,...}
FROM 表名
WHERE 字段名 [NOT] IN(元素1, 元素2, ...)
带BETWEEN AND 范围查询.
SELECT *|{字段名1, 字段名2, ……}
FROM 表名
WHERE 字段名 [NOT] BETWEEN 值1 AND 值2
带LIKE的字符匹配查询(模糊查询).
# 模糊查询
SELECT *|{字段名1, 字段名2, ……}
FROM 表名
WHERE 字段名 [NOT] LIKE '匹配字符串';
通配符.
通配符 | 说明 |
---|---|
% | 可以匹配一个字符或多个字符,可代表任意长度的字符串,长度可以为0。例如,“书%”表示以“书”开头的字符串。 |
_ | 仅可以匹配一个字符。例如,“书_”表示匹配字符串长度为2,以书开始的字符串。 |
百分号(%)适用于不定长的模糊查询
下划线(_)适用于定长的查询。
转义字符.
有时存在字符串中本身就含有 _ 或 %,那么我们就需要转义字符
SELECT * FROM USER WHERE name like %n\__1% ESCAPE '\' -- 第一个_ 被转义
带AND的多条件查询.
SELECT *|{字段名1, 字段名2, ...}
FROM 表名
WHERE 条件表达式1 AND 条件表达式2 [... AND 条件表达式n];
带OR的条件查询.
SELECT *|{字段名1, 字段名2, ...}
FROM 表名
WHERE 条件表达式1 OR 条件表达式2 [... OR 条件表达式n];
带NOT (少).
SELECT *|{字段名1, 字段名2, 字段名3,...} FROM 表名 WHERE NOT 字段 = 值;
# 相当于
WHERE 字段 <> 值
排序与限量 (单表查询)(DQL).
ORDER BY排序查询.
SELECT 字段名1, 字段名2, ...
FROM 表名
ORDER BY 字段名1 [ASC | DESC] [, 字段名2 [ASC | DESC
...];
# ASC 升序
# DESC 降序
# 当有多个字段进行排序时,首先按照字段名 1 进行排序,当遇到字段 1值相同时,再按照字段 2 进行排序
如果要实现分组结果的排序,ORDER BY 子句中必须使用 聚集函数或GROUP BY 子句的分组列
LIMIT限量查询(分页查询).
# 对于一次性查询出的大量记录,不仅不便于阅读查看,还会浪费系统效率
# 指定查询结果从哪一条记录开始,以及每次查询出的记录数量
SELECT 字段名1, 字段名2, ……
FROM 表名
LIMIT [OFFSET, ] 记录数;
# OFFSET 默认从0开始 表示 第一条记录 开始
#如
LIMIT 3 OFFSET 2; # 从第三条记录开始 最多 3条记录
LIMIT 2, 3;
分页查询
LIMIT 和 OFFSET对应关系
LIMIT pageSize
对应的 OFFSET pageSize*i (i 从 0 开始的整数)
聚合函数与分组(单表查询)(DQL).
聚合函数.
聚合函数就是把数据聚合起来的函数
函数名 | 作用 |
---|---|
COUNT() | 计算表中记录的个数或者列中值的个数。 |
SUM() | 获取符合条件所有结果的和。 |
AVG() | 计算一列中数据值的平均值。 |
MAX() | 获取查询数据中的最大值。 |
MIN() | 获取查询数据中的最小值。 |
GROUP BY 分组查询.
SELECT 字段名1, 字段名2, ...
FROM 表名
GROUP BY 字段名1 [, 字段名2, ... [HAVING 条件表达式]];
# HAVING 关键字指定条件表达式对分组后的内容进行过滤
# HAVING 与 WHERE 虽然作用相同,但是它们还是有一定的区别,HAVING 关键字后可以跟聚合函数,而 WHERE 则不可以
需要注意的是,使用 GROUP BY 直接进行分组查询后,
显示的结果是分组后的第一条记录的值
。因此,搭配 COUNT() 或 GROUP_CONCAT() 等聚合函数一起使用,才能获得每个组的查询结果。
多表查询 (DQL).
# 笛卡尔积
SELECT * FROM 表1,表2;
合并查询.
将多个 SELECT 语句的查询结果合并到一起
SELECT ...
UNION [ALL | DISTINCT]
SELECT ...
[UNION [ALL | DISTINCT]
SELECT ...
...];
# 默认 DISTINCT
查询结果集中的字段名称总是与第一个 SELECT 语句中的字段名称相同。
每个SELECT语句必须拥有相同数量的字段
,和相似的数据类型(如unsigned int, int)
。另外,每条 SELECT 语句中相同数据类型的字段顺序也必须相同。SELECT
cid
,name
FROMarticle
WHEREcid
= 1
UNION
SELECTcid
,cname
FROMcategory
WHEREcid
= 1;当使用 UNION 连接的多个查询结果中存在相同的记录时,会自动合并成一条记录。如果不需要合并可以使用 UNION ALL 进行合并查询。
连接查询.
- 交叉连接查询
- 内连接查询
- 外连接查询
交叉连接.
交叉连接(CROSS JOIN): 被连接的两个表中所有数据行的笛卡尔积
SELECT 查询字段 FROM 表1 CROSS JOIN 表2;
内连接.
内连接(INNER JOIN)又称简单连接或自然连接
SELECT 查询字段 FROM 表1 [INNER] JOIN 表2 ON 表1.关系字段 = 表2.关系字段 WHERE 条件;
ON 与 WHERE 虽然都是用于连接查询条件,但是它们的使用是有区别的
ON 用于过滤两表连接的条件
WHERE 用于过滤中间表的记录数据
由于内连接查询是默认的连接方式,因此可以省略INNER 关键字
在内连接查询中还有一种特殊的查询:
自连接查询。它是指相互连接的表在物理上为同一个表,但逻辑上分为两个表
外连接.
与内连接不同的是,外连接(OUTER JOIN)生成的结果集不仅可以包括符合连接条件的数据记录,
而且还可以包括左表、右表或两表中所有的数据记录。
根据使用需求不同,外连接可以分为:左连接“LEFT (OUTER) JOIN”、右连接查询“RIGHT (OUTER) JOIN”
SELECT 所查字段 FROM 表1 LEFT|RIGHT|FULL [OUTER] JOIN 表2
ON 表1.关系字段 = 表2.关系字段 WHERE 条件
# 关键字“LEFT|RIGHT [OUTER] JOIN”左边的表被称为左表,关键字右边的表被称为右表
①左连接
左连接查询用于返回左表中的所有记录,以及右表中符合连接条件的记录。
当左表的某行记录在右表中没有匹配的记录时,右表中相关的记录将设为空值。
②右连接
右连接查询用于返回右表中的所有记录,以及左表中符合连接条件的记录
当右表的某行记录在左表中没有匹配的记录时,左表中相关的记录将设为空值。
③全连接
把两张表的所有记录全部选择出来,并且,自动把对方不存在的列填充为NULL
区别.
INNER JOIN只返回同时存在于两张表的行数据
RIGHT OUTER JOIN返回右表都存在的行。如果某一行仅在右表存在,那么结果集就会以
NULL
填充剩下的字段LEFT OUTER JOIN则返回左表都存在的行
FULL OUTER JOIN,它会把两张表的所有记录全部选择出来,并且,自动把对方不存在的列填充为NULL
子查询.
包含在一条 SQL 语句中的 SELECT 语句
子查询必须书写在括号内
使用子查询时,外层语句的 WHERE 后面除了比较运算符外,还可以使用 IN、EXISTS、ANY、ALL 等操作符。
单行子查询.
# 将一个 SELECT 查询语句的结果作为另一个查询语句的 WHERE 条件
SELECT * FROM `article` WHERE `cid`=
(SELECT `cid` FROM `category` WHERE `cname`='生活');
带IN关键字的子查询.
SELECT * FROM `article` WHERE `cid` IN
(SELECT `cid` FROM `category` WHERE `cname` IN('科技', '生活'));
带EXISTS关键字的子查询.
# EXISTS 关键字后面连接的子查询语句不返回查询记录,而是返回一个真假值
# 当子查询语句查询到满足条件的记录时,就返回 TRUE,执行外层SQL 语句;否则返回 FALSE,不执行外层的 SQL 语句
UPDATE `article` SET `name`='修改标题' WHERE `cid`=999 AND
EXISTS (SELECT 1 FROM `category` WHERE `cid`=999);
带ANY关键字的子查询.
使用 ANY 关键字时,只要其后的子查询满足其中任意一个判断条件,就返回结果作为外层 SQL 语句的执行条件
SELECT * FROM `article` WHERE `cid` = ANY
(SELECT `cid` FROM `category`);
#上述 SQL 语句执行后,查询出了在栏目表中有相应记录的文章信息。如果文章表中有一些文章的 cid 在栏目表中不存在相应记录,则不会被查询出来。
带ALL关键字的子查询.
ALL 关键字在使用时,只有满足内层查询语句返回的所有结果,才可以执行外层查询语句
SELECT * FROM `article`
WHERE `cid` <> ALL (SELECT `cid` FROM `category`);
子查询返回单值的时候可用比较运算符,但返回多值的时候要用ANY或ALL,使用ANY和ALL时必须同时使用比较运算符
与ANY和ALL相关的比较运算符.
运算符 | 等效功能 | 功能含义 |
---|---|---|
> ANY | > 最小值(MIN) | 大于子查询结果中的某个值 |
> ALL | > 最大值(MAX) | 大于子查询结果中的所有值 |
< ANY | < 最小值(MIN) | 小于子查询结果中的某个值 |
< ALL | < 最小值(MIN) | 小于子查询结果中的所有值 |
>= ANY | >= 最小值(MIN) | 大于等于子查询结果中的某个值 |
>= ALL | >= 最大值(MAX) | 大于等于子查询结果中的所有值 |
<= ANY | <= 最小值(MIN) | 小于等于子查询结果中的某个值 |
<= ALL | <= 最小值(MIN) | 小于等于子查询结果中的所有值 |
= ANY | IN | 等于子查询结果中的某个值 |
= ALL | 等于子查询结果中的所有值(无意义) | |
!=ANY 或 <>ANY | 不等于子查询结果中的某个值 | |
!=ALL 或 <>ALL | NOT IN | 不等于子查询结果中的任何一个值 |
事务.
数据库事务.
把多条语句作为一个整体进行操作的功能
数据库事务可以确保该事务范围内的所有操作都可以全部成功或者全部失败。
如果事务失败,那么效果就和没有执行这些SQL一样,不会对数据库数据有任何改动。
事务的4个特性 : 用于保证多条SQL的全部执行.
- A:Atomic,原子性,将所有SQL作为原子工作单元执行,要么全部执行,要么全部不执行;
- C:Consistent,一致性,事务完成后,所有数据的状态都是一致的,即A账户只要减去了100,B账户则必定加上了100;
- I:Isolation,隔离性,如果有多个事务并发执行,每个事务作出的修改必须与其他事务隔离;
- D:Duration,持久性,即事务完成后,对数据库数据的修改被持久化存储。
隐式事务.
对于单条SQL语句,数据库系统自动将其作为一个事务执行
显式事务.
要手动把多条SQL语句作为一个事务执行,使用
BEGIN
开启一个事务,使用COMMIT
提交一个事务
COMMIT
是指提交事务,即试图把事务内的所有SQL所做的修改永久保存。如果
COMMIT
语句执行失败了,整个事务也会失败。有时希望主动让事务失败,这时,可以用
ROLLBACK
回滚事务,整个事务会失败数据库事务是由数据库系统保证的,我们只需要根据业务逻辑使用它就可以
# 显式事务
BEGIN;
SQL语句1;
...
COMMIT;
# 回滚
BEGIN;
SQL语句1;
...
ROLLBACK;
隔离级别.
对于两个并发执行的事务,如果涉及到操作同一条记录的时候,可能会发生问题。因为并发操作会带来数据的不一致性,包括脏读、不可重复读、幻读等。数据库系统提供了隔离级别来让我们有针对性地选择事务的隔离级别,避免数据不一致的问题。
SQL标准定义了4种隔离级别,分别对应可能出现的数据不一致的情况:
Isolation Level | 脏读(Dirty Read) | 不可重复读(Non Repeatable Read) | 幻读(Phantom Read) |
---|---|---|---|
Read Uncommitted | Yes | Yes | Yes |
Read Committed | - | Yes | Yes |
Repeatable Read | - | - | Yes |
Serializable | - | - | - |
students表数据
select * from students;
+----+-------+
| id | name |
+----+-------+
| 1 | Alice |
+----+-------+
1 row in set (0.00 sec)
Read Uncommitted是隔离级别最低的一种事务级别。在这种隔离级别下,一个事务会读到另一个事务更新后但未提交的数据,如果另一个事务回滚,那么当前事务读到的数据就是脏数据,这就是脏读(Dirty Read)
脏数据:从目标中取出的数据已经过期、错误或者没有意义 ,即,与数据库中内容不一致的数据
时刻 | 事务A | 事务B |
---|---|---|
1 | SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; | SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; |
2 | BEGIN; | BEGIN; |
3 | UPDATE students SET name = ‘Bob’ WHERE id = 1; | |
4 | SELECT * FROM students WHERE id = 1; | |
5 | ROLLBACK; | |
6 | SELECT * FROM students WHERE id = 1; | |
7 | COMMIT; |
在Read Committed隔离级别下,一个事务可能会遇到不可重复读(Non Repeatable Read)的问题。
不可重复读是指,在一个事务内,多次读同一数据,在这个事务还没有结束时,如果另一个事务恰好修改了这个数据,那么,在第一个事务中,两次读取的数据就可能不一致。
时刻 | 事务A | 事务B |
---|---|---|
1 | SET TRANSACTION ISOLATION LEVEL READ COMMITTED; | SET TRANSACTION ISOLATION LEVEL READ COMMITTED; |
2 | BEGIN; | BEGIN; |
3 | SELECT * FROM students WHERE id = 1; | |
4 | UPDATE students SET name = ‘Bob’ WHERE id = 1; | |
5 | COMMIT; | |
6 | SELECT * FROM students WHERE id = 1; | |
7 | COMMIT; |
在Repeatable Read隔离级别下,一个事务可能会遇到幻读(Phantom Read)的问题。
幻读是指,在一个事务中,第一次查询某条记录,发现没有,但是,当试图更新这条不存在的记录时,竟然能成功,并且,再次读取同一条记录,它就神奇地出现了。
时刻 事务A 事务B 1 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; 2 BEGIN; BEGIN; 3 SELECT * FROM students WHERE id = 99; 4 INSERT INTO students (id, name) VALUES (99, ‘Bob’); 5 COMMIT; 6 SELECT * FROM students WHERE id = 99; 7 UPDATE students SET name = ‘Alice’ WHERE id = 99; 8 SELECT * FROM students WHERE id = 99; 9 COMMIT; Serializable(可串行化)是最严格的隔离级别。在Serializable隔离级别下,所有事务按照次序依次执行,因此,脏读、不可重复读、幻读都不会出现。
虽然Serializable隔离级别下的事务具有最高的安全性,但是,由于事务是串行执行,所以效率会大大下降,应用程序的性能会急剧降低。如果没有特别重要的情景,一般都不会使用Serializable隔离级别
默认隔离级别.
如果没有指定隔离级别,数据库就会使用默认的隔离级别。在MySQL中,如果使用InnoDB,默认的隔离级别是Repeatable Read
补充.
1.select可用于计算.
select 100 + 100; //结果 200
# 这个主要用来判断当前到数据库的连接是否有效
2.那么多连接查询,如何合理使用?.
我们把tableA看作左表,把tableB看成右表,那么INNER JOIN是选出两张表都存在的记录:
LEFT OUTER JOIN是选出左表存在的记录:
RIGHT OUTER JOIN是选出右表存在的记录:
FULL OUTER JOIN则是选出左右表都存在的记录:
3.关系数据库的基本操作就是增删改查,即CRUD:Create、Retrieve、Update、Delete。其中,对于查询,我们已经详细讲述了SELECT
语句的详细用法。.
4.插入或替换.
# 添加新记录是 但是 记录已存在时,如果不想先删除已存在记录,再插入新记录 可使用
REPLACE INTO 表名(字段1,字段2,...) VALUES(值1,值2,...);
#当记录是不存在的时候,replace 作用 与 insert 一致
5.插入或更新.
# 同样与replace 应用在 添加新纪录 ,但已存在的情况下
INSERT INTO 表名(字段1,字段2,...) VALUES(值1,值2,...)
ON DUPLICATE KEY UPDATE 字段1=新值1 [,字段2=新值2, ...];
# 当记录不存在的时候, 添加时 只使用 insert 的字段 及 值
# 当记录存在的时候, 添加时 使用 update 的字段 及 值
6.插入或忽略.
# 如果我们希望插入一条新记录(INSERT),但如果记录已经存在,就啥事也不干直接忽略
INSERT IGNORE INTO
表名(字段1,字段2,...) VALUES(值1,值2,...);
7.快照 (查询结果集建立新表).
# 如果想要对一个表进行快照,即复制一份当前表的数据到一个新表
CREATE TABLE 新表
SELECT * FROM 旧表 [WHERE 条件表达式];
8.写入查询结果集.
# 如果查询结果集需要写入到表中,可以结合INSERT和SELECT,将SELECT语句的结果集直接插入到指定表中。
# 创建一个统计成绩的表statistics,记录各班的平均成绩:
CREATE TABLE statistics (
id BIGINT NOT NULL AUTO_INCREMENT,
class_id BIGINT NOT NULL,
average DOUBLE NOT NULL,
PRIMARY KEY (id)
);
# 我们就可以用一条语句写入各班的平均成绩
INSERT INTO statistics (class_id, average) SELECT class_id, AVG(score) FROM students GROUP BY class_id;
确保
INSERT
语句的列和SELECT
语句的列能一一对应
9.强制使用指定索引.
# 在查询的时候,数据库系统会自动分析查询语句,并选择一个最合适的索引。但是很多时候,数据库系统的查询优化器并不一定总是能使用最优索引。如果我们知道如何选择索引,可以使用FORCE INDEX强制查询使用指定的索引
SELECT * FROM students FORCE INDEX (idx_class_id) WHERE class_id = 1 ORDER BY id DESC;
指定索引的前提是索引
idx_class_id
必须存在。
10. 模式的创建与删除.
创建一个模式相当于创建一个命名空间,我们可以在这个命名空间创建数据库对象,如表,视图,索引。。。
创建:
CREATE SCHEMA [模式名] AUTHORIZATION 用户名 -- 如果没有定义模式名,默认使用用户名充当
[表定义语句]|[视图定义语句]|[授权定义子句]
删除:
DROP SCHEMA 模式名 CASCADE | RESTRICT -- 级联:删除模式下的所有数据库对象;限制:若存在数据库对象,不删除模式
11.索引的创建与删除.
索引是基本表的目录
作用:
- 加快数据查询速度
- 数据量大时,数据文件很大,不使用索引,每次查询,会将数据文件分块读到内存中,查找
- 如果使用索引,那么先加载索引文件(比数据文件小很多),根据索引找到元祖数据的位置,然后加载元祖数据所在的块,加载到内存查找;
- 保证数据的唯一性
- 定义索引,需要定义数据的唯一性
- 加快连接速度
- 两个表的连接操作,(连接字段建立索引)
建立原则:
- 大表建索引
- 基本表,不要创建过多索引
- 更具查询条件要求建立索引(查询频度高的)
创建索引:
CREATE CLUSTER | UNIQUE INDEX IX_xxx ON 表名(列名 [DESC | ASC][,列名。。。]) -- CLUSTER 聚簇索引:索引项的排序顺序与基本表的物理顺序一致
删除索引:
DROP INDEX IX_xxx -- 增删改频繁的,系统会花费大量时间维护索引,查询效率变低,因此需要删除这些不必要的索引
12.视图的创建与删除.
视图是从一个表或几个基本表(或视图)中选定某些记录或列而导出的特殊类型的表。
视图本身并不存储数据,数据仍存储在原来的基本表上,试图数据是虚拟的,视图只是提供了一种访问基本表中数据的方法。
视图是一个虚表,数据库只存放视图的定义,但是视图视图创建后,用户可以像基本表一样对视图进行查询,在特殊情况下甚至可以增删改
优点:
- 增加数据安全性
- 可以限制用户直接存取基本表的某些列或记录
- 屏蔽数据的复杂性
- 通过视图可得到多个基本表经过计算后的数据
视图创建:
CREATE VIEW 视图名 [(列名[,列名[...]])] -- 指定列
AS SQL子查询语句
[WITH CHECK OPTION] -- 这一行表示 对视图进行增删改操作是要保证,操作的行,要满足子查询语句的条件表达式中的条件,即会自定添加 子查询的条件,如下
create view view_1
as
select sno,sname
from student
where sno > 20
with check option
==> 对视图进行增删改操作时,需要满足 sno > 20 的条件
删除视图:
DROP VIEW 视图名 [CASCADE] -- 级联:删除本视图和导出引用的所有视图
-- 删除视图,只是删除视图的定义,数据还在(只有数据库管理员才能删除)
-- 视图对对应的 基本表删除后,视图将无意义,因此我们也需要删除视图
视图的查询与更新.
试图查询:
使用与基本表一致
视图更新:
实际上是 基本表的更新; 但是
只能在一个基本表上进行增删改的操作
,增删改时为了避免对不属于视图的部分进行操作,定义视图都需要加上with check option
使用与基本表一致
其他注意点:
如果视图定义中
SELECT
语句中含有GROUP BY
、DISTINCT
或 聚集函数 等,可进行删除操作,不能进行插入、修改操作如果视图中包含由表达式计算的列,那么也不允许进行 修改操作
如果视图中没有包含基本表的所有非空列,则不能 进行插入操作
如果视图定义中含有嵌套语句,且内层查询的from子句中涉及的表也是导出的视图的基本表,则不允许更新
总之,使用视图,就别想着增删改
13.方法.
DATEPART
: SQLServer
中 获取日期部分
DATAPART(year,getdate())
getdate
:获取当前时间
LOWER
:字符串小写
14. 小提示.
WHERE
和GROUP
子句中都不能出现 聚集函数,其他的可以- 如果要实现分组结果的排序,ORDER BY 子句中必须使用 聚集函数或GROUP BY 子句的分组列
15. 三范式.
第一范式 1NF
为了解决高层项导致的更新困难,如 经理 分为 正副 ==> 直接 正经理和副经理
属性是不可再分的原子值
第二范式 2NF
为了解决存在的大量冗余数据和操作异常问题
非主键完全依赖于主键,主键值可以确定唯一非主键值,一个主键确定唯一一行
第三范式 3NF
所有字段只能依赖于主属性,不能依赖其他字段。消除依赖传递
16.并发操作问题.
影响数据一致性
丢失修改
两个事务T1和T2读入同一个数据并修改,事务T2提交的修改结果覆盖了事务T1提交的修改结果,导致提交的修改结果,导致事务T1的修改结果丢失。
脏读
事务T1更改某一数据,并写入数据库,事务T2读取同一数据,但事务T1由于某种原因被撤销,此时事务T1更改过的数据恢复到原来的值,使事务T2读取到的值与数据库中的值不同,只是操作过程中的一个过渡性的、不需要的、脏的数据。
不可重复读
事务T1读取数据后,事务T2执行更改操作,使事务T1无法在现前一次读取的结果
- 事务T1读取某一数据之后,事务T2对其修改,T1再次读取时,结果与上次不一致
- 事务T1按一定条件读取某些数据记录以后,事务T2删除了其中的部分记录,使事务T1按相同条件再次读取记录时,发现某些记录不存在了。(幻读的一种格式)
- 事务T1按一定条件读取某些数据记录以后,事务T2插入了一些记录,使事务T1按相同的条件再次读取记录时,发现增加了记录。(幻读的一种格式,普遍)
幻读
事务T1插入数据提交前,事务T2获取数据;T1事务提交后,T2获取数据(结果多了新纪录)– repeatable read隔离之前
使用 repeatable read隔离之后:
T2获取的数据,是不会有任何变化的,但是当我们更新数据后,在查询,就会发现原本没有的记录,就有了记录(细节看之前的事务)
17.锁.
排他锁(行级)
写锁,X锁
- 如果事务T对数据库对象加X锁,只允许事务T读取和更改操作,其他事务无法加锁 —— 保证数据不被同时读写
共享锁(表级)
读锁,S锁
如果事务T对数据库对象加S锁,只允许事务T读取,其他事务可以加S锁,但不能加X锁 ——
保证数据不被同时写,可以读
锁定机制:
一级锁定协议:
事务T在修改数据对象之前,必须先对其加X锁,并直到事务结束时才释放X锁;如果事务T只是读取,就不加锁; —— 只能防止丢失修改,但不能防止脏读、不可重复读。
事务T 在修改数据之前 加了X锁,直到修改完毕T事务提交,此时数据已经修改完毕,不会被其他事务覆盖 —— 无丢失修改
事务T 在查数据,由于没有加锁,事务T之前其他事务更新数据,事务T查到数据,接着其他事务回滚,事务T在查,发现该结果,与数据库中不一致 —— 存在脏读
二级锁定协议:
在一级的基础上,加上事务T在读取之前必须加S锁,读完后立刻释放S锁 —— 进一步防止脏读,但是不防不可重复读
事务T 在查数据之前加S锁,在事务T读的过程中,其他事务只能读,不能写(不可能存在回滚),所以事务T读完后,读取的数据是一直在数据库中存在的 —— 没有脏读
事务T 在读取数据加S锁,但是这个S锁,在读后就释放了,因此在事务T释放S锁后,进行更新操作,事务T再次读取数加S锁,此时读到的数据和之前的数据不一致 —— 不可重复读
三级锁定协:
在一级的基础上,加上事务T在读取之前必须加S锁,直到事务T结束后才释放S锁
查询等到事务T结束,才会释放S锁,即在整个事务T中间,其他的事务只能读,不能写,因此读取的数据始终是相同的 —— 没有不可重复读