0%

MySQL

一、MySQL 入门

MySQL 是最流行的关系型数据库管理系统,在 WEB 应用方面 MySQL 是最好的 RDBMS(Relational Database Management System)应用软件之一。

1、什么是数据库?

数据库(DataBase,简称DB)

概念:长期存放在计算机内,有组织,可共享的大量数据的集合,是一个数据“仓库”。

作用:保存,并能安全管理数据(增删改查等),减少冗余…

数据库总览

  • 关系型数据库(SQL)
    • MySQL,Oracle,SQL Server,SQLite,DB2…
    • 关系型数据库通过外键关联来建立表与表之间的关系。
  • 非关系型数据库(NOSQL,Not Only SQL)
    • Redis,MongoDB…
    • 非关系型数据库通常指数据以对象的形式存储在数据库中,而对象之间的关系通过每个对象自身的属性来决定。

2、什么是 DBMS?

数据库管理系统(DataBase Management System)

数据库管理软件,科学组织和存储数据,高效地获取和维护数据。

不那么严格的话,MySQL也算是一个数据库管理系统。

3、MySQL简介

概念:是现在流行的、开源的、免费的、关系型数据库。

历史:由瑞典MySQL AB公司开发,目前属于 Oracle 旗下产品。

特点

  • MySQL 是开源的,目前隶属于 Oracle 旗下产品。
  • MySQL 支持大型的数据库。可以处理拥有上千万条记录的大型数据库。
  • MySQL 使用标准的 SQL 数据语言形式。
  • MySQL 可以运行于多个系统上,并且支持多种语言。这些编程语言包括 C、C++、Python、Java、Perl、PHP、Eiffel、Ruby 和 Tcl 等。
  • MySQL 对PHP有很好的支持,PHP 是目前最流行的 Web 开发语言。
  • MySQL 支持大型数据库,支持 5000 万条记录的数据仓库,32 位系统表文件最大可支持 4GB,64 位系统支持最大的表文件为8TB。
  • MySQL 是可以定制的,采用了 GPL 协议,你可以修改源码来开发自己的 MySQL 系统。

官网 : https://www.mysql.com/

4、安装MySQL

这里建议使用压缩版,安装快,方便,不复杂。

exe 可执行文件安装 MySQL 谁用谁知道,卸载是真的麻烦,┭┮﹏┭┮。

mysql5.7 64位下载地址:

https://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-5.7.19-winx64.zip

安装步骤

1、下载后得到zip压缩包.

2、解压到自己想要安装到的目录,此处我解压到的是 D:\Environment\mysql-5.7.19

3、添加环境变量:我的电脑->属性->高级->环境变量

1
选择PATH,在其后面添加: 你的mysql 安装文件下面的bin文件夹

4、在 bin 文件夹下创建 my.ini 配置文件

1
2
3
4
5
[mysqld]
basedir=D:\Environment\mysql-5.7.19\
datadir=D:\Environment\mysql-5.7.19\data\
port=3306
skip-grant-tables

5、启动管理员模式下的 CMD,并将路径切换至 mysql 下的 bin 目录,然后输入 mysqld –install (安装mysql)

6、再输入 mysqld –initialize-insecure –user=mysql 初始化数据文件

7、然后再次启动 mysql(net start mysql) 然后用命令 mysql –u root –p 进入 mysql 管理界面(密码暂时为空)

8、进入界面后更改root密码

1
update mysql.user set authentication_string=password('123456') where user='root' and Host = 'localhost';

9、刷新权限

1
flush privileges;

10、修改 my.ini文件删除最后一句skip-grant-tables

11、重启mysql即可正常使用

1
2
net stop mysql
net start mysql

12、连接上测试出现以下结果就安装好了

1
2
3
4
5
6
7
8
9
10
update user set password=password('123456')where user='root'; # 修改密码
flush privileges; # 刷新数据库
show databases; # 显示所有数据库
use dbname;# 打开某个数据库
show tables; # 显示数据库Mysql中所有的表
describe user; # 显示表Mysql数据库中User表的列信息
create database name; # 创建数据库
use databasename; # 选择数据库
exit; # 退出 Mysql
? # 命令关键词,寻求帮助

二、基本概念

1、数据类型

数值类型

MySQL 支持所有标准 SQL 数值数据类型。

关键字INT是INTEGER的同义词,关键字DEC是DECIMAL的同义词。

类型 大小 范围(有符号) 范围(无符号) 用途
TINYINT 1 Bytes (-128,127) (0,255) 小整数值
SMALLINT 2 Bytes (-32 768,32 767) (0,65 535) 大整数值
MEDIUMINT 3 Bytes (-8 388 608,8 388 607) (0,16 777 215) 大整数值
INT或INTEGER 4 Bytes (-2 147 483 648,2 147 483 647) (0,4 294 967 295) 大整数值
BIGINT 8 Bytes (-9,223,372,036,854,775,808,9 223 372 036 854 775 807) (0,18 446 744 073 709 551 615) 极大整数值
FLOAT 4 Bytes (-3.402 823 466 E+38,-1.175 494 351 E-38),0,(1.175 494 351 E-38,3.402 823 466 351 E+38) 0,(1.175 494 351 E-38,3.402 823 466 E+38) 单精度 浮点数值
DOUBLE 8 Bytes (-1.797 693 134 862 315 7 E+308,-2.225 073 858 507 201 4 E-308),0,(2.225 073 858 507 201 4 E-308,1.797 693 134 862 315 7 E+308) 0,(2.225 073 858 507 201 4 E-308,1.797 693 134 862 315 7 E+308) 双精度 浮点数值
DECIMAL 对DECIMAL(M,D) ,如果M>D,为M+2否则为D+2 依赖于M和D的值 依赖于M和D的值 小数值

日期类型

每个时间类型有一个有效值范围和一个”零”值,当指定不合法的MySQL不能表示的值时使用”零”值。

类型 大小 ( bytes) 范围 格式 用途
DATE 3 1000-01-01 ~ 9999-12-31 YYYY-MM-DD 日期值
TIME 3 ‘-838:59:59’ ~ ‘838:59:59’ HH:MM:SS 时间值或持续时间
YEAR 1 1901 ~ 2155 YYYY 年份值
DATETIME 8 1000-01-01 00:00:00 ~ 9999-12-31 23:59:59 YYYY-MM-DD HH:MM:SS 混合日期和时间值
TIMESTAMP 4 1970-01-01 00:00:00~2038结束时间是第 2147483647 秒,北京时间 2038-1-19 11:14:07,格林尼治时间 2038年1月19日 凌晨 03:14:07 YYYYMMDD HHMMSS 混合日期和时间值,时间戳

字符串类型

字符串类型指 CHAR、VARCHAR、BINARY、VARBINARY、BLOB、TEXT、ENUM 和 SET。

类型 大小(字节) 用途
CHAR 0 ~ 255 定长字符串
VARCHAR 0 ~ 65535 变长字符串
TINYBLOB 0 ~ 255 不超过 255 个字符的二进制字符串
TINYTEXT 0 ~ 255 短文本字符串
BLOB 0 ~ 65 535 二进制形式的长文本数据
TEXT 0 ~ 65 535 长文本数据
MEDIUMBLOB 0 ~ 16 777 215 二进制形式的中等长度文本数据
MEDIUMTEXT 0 ~ 16 777 215 中等长度文本数据
LONGBLOB 0 ~ 4 294 967 295 二进制形式的极大文本数据
LONGTEXT 0 ~ 4 294 967 295 极大文本数据

注意:char(n) 和 varchar(n) 中括号中 n 代表字符的个数,并不代表字节个数,比如 CHAR(30) 就可以存储 30 个字符。

CHAR 和 VARCHAR 类型类似,但它们保存和检索的方式不同。它们的最大长度和是否尾部空格被保留等方面也不同。在存储或检索过程中不进行大小写转换。

BINARY 和 VARBINARY 类似于 CHAR 和 VARCHAR,不同的是它们包含二进制字符串而不要非二进制字符串。也就是说,它们包含字节字符串而不是字符字符串。这说明它们没有字符集,并且排序和比较基于列值字节的数值值

BLOB 是一个二进制大对象可以容纳可变数量的数据。有 4 种 BLOB 类型:TINYBLOB、BLOB、MEDIUMBLOB 和 LONGBLOB。它们区别在于可容纳存储范围不同。

有 4 种 TEXT 类型:TINYTEXT、TEXT、MEDIUMTEXT 和 LONGTEXT。对应的这 4 种 BLOB 类型,可存储的最大长度不同,可根据实际情况选择。

NULL 值

  • 理解为”没有值”或”未知值”
  • 不要用 NULL 进行算术运算,结果仍为 NULL,没有意义

2、数据字段属性

Unsigned

  • 无符号的
  • 声明该数据列不允许负数

Zerofill

  • 不足的位数用0来填充,例如int(3),5则为005

Auto_InCrement

  • 自动增长的,每添加一条数据,自动在上一个记录数上加1(默认)
  • 通常用于设置主键,且为整数类型
  • 可定义起始值和步长
    • 起始值设置:AUTO_INCREMENT = 100,只影响当前表。
    • 步长设置:SET @@auto_increment_increment = 5,影响所有使用自增的表。

NULL 和 NOTE NULL

  • 默认为 NULL,即没有插入该列的数值。
  • 如果设置为 NOT NULL,则该列必须有值。

DEFAULT

  • 用于设置默认值
  • 例如,性别字段,默认为”男”,若无指定该列的值,则默认为”男”。

3、数据表引擎

MySQL的数据表的类型 : MyISAM , InnoDB , HEAP , BOB , CSV等…

常见的 MyISAM 与 InnoDB 类型:

名称 MyISAM InnoDB
事务处理 不支持 支持
数据行锁定 不支持 支持
外键约束 不支持 支持
全文索引 支持 不支持
表空间大小 较小 约为 MyISAM 两倍

适用场合:

  • MyISAM:节约空间,速度较快。
  • InnoDB:安全性,事务处理及多用户操作数据表。

4、数据表的物理存储

MySQL 数据表以文件方式存储在磁盘中

  • 包括表文件,数据文件,以及数据库的选项文件
  • 位置:MySQL\data,目录名对应数据库名,该目录下文件名对应数据表

InnoDB

  • .frm 文件 – 表结构定义文件
  • .idb 文件 – MySQL数据文件、索引文件

InnoDB 类型数据表只有一个 *.frm 文件(不算 .idb 备份文件), 以及上一级目录的 ibdata1文件。

MyISAM

  • . frm – 表结构定义文件
  • . MYD – 数据文件 ( data )
  • . MYI – 索引文件 ( index )

5、字符集

字符集是一套符合和编码,校验规则(collation)是在字符集内用于比较字符的一套规则,即字符集的排序规则。MySQL可以使用各种字符集和检验规则来组织字符。

MySQL服务器可以支持多种字符集,在同一台服务器,同一个数据库,甚至同一个表的不同字段都可以指定使用不同的字符集,相比oracle等其他数据库管理系统,在同一个数据库只能使用相同的字符集,MySQL明显存在更大的灵活性。

每种字符集都可能有多种校对规则,并且都有一个默认的校对规则,并且每个校对规则只是针对某个字符集,和其他的字符集没有关系。

而 MySQL 默认的字符集使用的是 Latin1,会导致中文乱码!因此需要为数据库、数据表、甚至数据列显式设定不同的字符集。或者在 my.ini 中修改默认的字符集。

1
character-set-server = utf8

三、基本操作

1、数据库

1
2
3
4
5
6
7
创建数据库 : create database [if not exists] 数据库名;

删除数据库 : drop database [if exists] 数据库名;

查看数据库 : show databases;

使用数据库 : use 数据库名;

2、创建数据表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
-- 目标 : 创建一个school数据库
-- 创建学生表(列,字段)
-- 学号int 登录密码varchar(20) 姓名,性别varchar(2),出生日期(datatime),家庭住址,email
-- 创建表之前 , 一定要先选择数据库

CREATE TABLE IF NOT EXISTS `student` (
`id` int(4) NOT NULL AUTO_INCREMENT COMMENT '学号',
`name` varchar(30) NOT NULL DEFAULT '匿名' COMMENT '姓名',
`pwd` varchar(20) NOT NULL DEFAULT '123456' COMMENT '密码',
`sex` varchar(2) NOT NULL DEFAULT '男' COMMENT '性别',
`birthday` datetime DEFAULT NULL COMMENT '生日',
`address` varchar(100) DEFAULT NULL COMMENT '地址',
`email` varchar(50) DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

-- 查看数据库的定义
SHOW CREATE DATABASE school;
-- 查看数据表的定义
SHOW CREATE TABLE student;
-- 显示表结构
DESC student;
-- 设置严格检查模式
SET sql_mode='STRICT_TRANS_TABLES';

3、修改数据表

1
2
3
4
5
6
7
8
9
10
11
ALTER TABLE student RENAME AS student1		-- 修改表名

ALTER TABLE student1 ADD newcol VARCHAR(50) -- 添加字段

ALTER TABLE student1 MODIFY newcol INT(10) -- 修改字段


ALTER TABLE student1 CHANGE newcol newnewcol VARCHAR(10) -- 修改字段
ALTER TABLE student1 DROP newnewcol -- 删除字段

DROP TABLE IF EXISTS student1 -- 删除数据表

可以使用```来包裹标识符,以避免和关键字重名!

四、外键

如果公共关键字在一个关系中是主关键字,那么这个公共关键字被称为另一个关系的外键。由此可见,外键表示了两个关系之间的相互联系。以另一个关系的外键作为主关键字的表称为主表,具有此外建的表被称为主表的从表

在实际操作中,将一个表的值放入第二个表来表示关联,所使用的的值是第一个表的主键值(在必要时可包括复合主键值)。此时,第二个表中保存这些值的属性称为外键(foreign key)。

外键可以保持数据一致性,完整性,主要目的是约束存储在外键表中的数据。使两张表形成关联,外键只能引用主表中的列的值或使用空值。

1、建表时指定外键约束

1
2
3
4
5
6
7
8
CREATE TABLE student (
id INT(10),
`name` VARCHAR(50),
tid INT(11),
PRIMARY KEY (id),
CONSTRAINT FK_tid
FOREIGN KEY (tid) REFERENCES teacher(id)
) ENGINE=INNODB DEFAULT CHARSET=utf8

注意事项

  • 关联的两张表引擎必须相同
  • 外键名不能重复
  • 关联的两个类型必须相同

2、建表后添加外键

1
ALTER TABLE `study`.`student` ADD CONSTRAINT `FK_tid` FOREIGN KEY (`tid`) REFERENCES `study`.`teacher`(`id`); 

3、删除外键

1
2
3
4
-- 删除外键
ALTER TABLE `study`.`student` DROP FOREIGN KEY `FK_tid`;
-- 删除外键索引,这个索引是建立外键时自动生成的
ALTER TABLE `study`.`student` DROP INDEX `FK_tid`;

以上的操作都是物理外键,数据库级别的外键,我们不建议使用!(避免数据库过多造成困扰,了解即可!)

最佳实践

  • 数据库就是单纯的表,只用来存数据,只有行(数据)和列(字段)
  • 我们想使用多张表的数据,想使用外键一般使用程序去实现

五、DML

数据库意义:数据存储、数据管理

管理数据库数据方法

  • 通过 SQLyog 等管理工具管理数据库数据
  • 通过 DML 语句管理数据库数据

DML 语言:Data Manipulaiton Language,数据操作语言

  • 用于操作数据库对象中所包含的数据
  • 包括:
    • INSERT(添加数据语句)
    • UPDATE(更新数据语句)
    • DELETE(删除数据语句)

1、INSERT

语法:

1
INSERT INTO 表名[(字段1,字段2,字段3,...)] VALUES('值1','值2','值3'),('值1','值2','值3')...

注意 :

  • 字段或值之间用英文逗号隔开 .
  • ‘ 字段1,字段2…’ 该部分可省略 , 但添加的值务必与表结构,数据列,顺序相对应,且数量一致 .
  • 可同时插入多条数据 , values 后用英文逗号隔开 .

2、UPDATE

语法:

1
UPDATE 表名 SET column_name=value [column_name2=value2,...] [WHERE condition];

注意 :

  • column_name 为要更改的数据列
  • value 为修改后的数据 , 可以为变量 , 具体指 , 表达式或者嵌套的SELECT结果
  • condition 为筛选条件 , 如不指定则修改该表的所有列数据

3、DELETE

语法:

1
DELETE FROM 表名 [WHERE condition];

注意:condition为筛选条件 , 如不指定则删除该表的所有列数据

1
2
-- 删除id为5的数据
DELETE FROM grade WHERE gradeid = 5

4、TRUNCATE

用于完全清空表数据 , 但表结构 , 索引 , 约束等不变 。

语法:

1
2
3
4
TRUNCATE [TABLE] table_name;

-- 清空年级表
TRUNCATE grade

注意:区别于DELETE命令

  • 相同 : 都能删除数据 , 不删除表结构 , 但 TRUNCATE 速度更快

  • 不同 :

    • 使用 TRUNCATE TABLE 重新设置 AUTO_INCREMENT 计数器
    • 使用 TRUNCATE TABLE 不会对事务有影响
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-- 创建一个测试表
CREATE TABLE `test` (
`id` INT(4) NOT NULL AUTO_INCREMENT,
`coll` VARCHAR(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8

-- 插入几个测试数据
INSERT INTO test(coll) VALUES('row1'),('row2'),('row3');

-- 删除表数据(不带where条件的delete)
DELETE FROM test;
-- 结论:如不指定Where则删除该表的所有列数据,自增当前值依然从原来基础上进行,会记录日志.

-- 删除表数据(truncate)
TRUNCATE TABLE test;
-- 结论:truncate删除数据,自增当前值会恢复到初始值重新开始;不会记录日志.

拓展:使用DELETE清空不同引擎的数据库表数据.重启数据库服务后
InnoDB : 自增列从初始值重新开始 (存储在内存中,断电即失)
MyISAM : 自增列依然从上一个自增数据基础上开始 (存在文件中,不会丢失)

六、DQL 【重点】

DQL(Data Qeury Language 数据查询语言)

  • 查询数据库数据,如 SELECT 语句
  • 简单的单表查询或多表的复杂查询和嵌套查询
  • 是数据库语言中最核心、最重要的语句
  • 使用频率最高的语句

SELECT 语法

1
2
3
4
5
6
7
8
9
10
SELECT [ALL | DISTINCT]
{* | table.* | [table.field1[as alias1][,table.field2[as alias2]][,...]]}
FROM table_name [as table_alias]
[left | right | inner join table_name2] -- 联合查询
[WHERE ...] -- 指定结果需满足的条件
[GROUP BY ...] -- 指定结果按照哪几个字段来分组
[HAVING] -- 过滤分组的记录必须满足的次要条件
[ORDER BY ...] -- 指定查询记录按一个或多个条件排序
[LIMIT {[offset,]row_count | row_countOFFSET offset}];
-- 指定查询的记录从哪条至哪条

注意 : [ ] 括号代表可选的 , { }括号代表必选得

1、简单查询语句

1
2
3
4
5
6
7
-- 查询表中所有的数据列结果,但是效率低,不推荐

-- 查询所有学生信息
SELECT * FROM student;

-- 查询指定列(学号 , 姓名)
SELECT studentno,studentname FROM student;

AS 子句作为别名

作用:

  • 可给数据列取一个新别名
  • 可给表取一个新别名
  • 可把经计算或总结的结果用另一个新名称来代替
1
2
3
4
5
6
7
-- 为列和表取别名
-- as可以省略不写
SELECT studentno AS 学号, studentname 姓名 FROM student s;

-- 为查询结果取一个新名字
-- concat()函数用于拼接字符串
SELECT CONCAT('姓名:', studentname) 新姓名 FROM student

DISTINCT 去重

作用:去掉 SELECT 查询返回的记录结果中重复的记录(返回所有列的值都相同)

1
2
3
4
5
-- 查看哪儿些同学参加了考试
SELECT studentno FROM result;

-- 去掉参加多门考试的同学
SELECT DISTINCT studentno FROM result;

表达式

数据库中的表达式一般由文本值,列值,NULL,函数和操作符等组成

应用场景:

  • SELECT 语句返回结果列中使用
  • SELECT 语句中的 ORDER BY,HAVING 等子句中使用
  • DML 语句中的 where 条件语句中使用表达式
1
2
3
4
5
6
7
8
9
10
11
-- 查询自增步长
SELECT @@auto_increment_increment;

-- 查询数据库版本号
SELECT VERSION()

-- 计算
SELECT 100 * 2 - 1 计算结果

-- 考试分数全部加1分
SELECT studentno 学号, studentresult 加分前, (studentresult + 1) 加分后 FROM result

2、where 条件语句

作用:用于检索数据库中符合条件的记录。

搜索条件可由一个或多个逻辑表达式组成,结果一般为真或假。

逻辑操作符

逻辑操作符 语法 描述
AND 或 && a AND b 或 a && b 逻辑与,同时为真结果才为真
OR 或 || a OR b 或 a || b 逻辑或,只要一个为真,则结果为真
NOT 或 ! NOT a 或 !a 逻辑非,若操作数为假,则结果为真

比较运算符

比较运算符 描述 实例
= 等号,检测两个值是否相等,如果相等返回true (A = B) 返回false。
<>, != 不等于,检测两个值是否相等,如果不相等返回true (A != B) 返回 true。
> 大于号,检测左边的值是否大于右边的值, 如果左边的值大于右边的值返回true (A > B) 返回false。
< 小于号,检测左边的值是否小于右边的值, 如果左边的值小于右边的值返回true (A < B) 返回 true。
>= 大于等于号,检测左边的值是否大于或等于右边的值, 如果左边的值大于或等于右边的值返回true (A >= B) 返回false。
<= 小于等于号,检测左边的值是否小于或等于右边的值, 如果左边的值小于或等于右边的值返回true (A <= B) 返回 true。

注意:<> 和 != 都是不等于的意思。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
-- 查询考试成绩区间在[95, 100]
SELECT studentno, studentresult
FROM result
WHERE studentresult BETWEEN 95 AND 100

-- 使用逻辑与
SELECT studentno, studentresult
FROM result
WHERE studentresult >= 95 AND studentresult <= 100

-- AND 换成 &&
SELECT studentno, studentresult
FROM result
WHERE studentresult >= 95 && studentresult <= 100

-- 逻辑或
SELECT studentno, studentresult
FROM result
WHERE studentresult < 60 OR studentno = 1003

-- OR 换成 ||
SELECT studentno, studentresult
FROM result
WHERE studentresult < 60 || studentno = 1003

-- 逻辑非
SELECT studentno, studentresult
FROM result
WHERE studentno != 1000

-- ! 换成 NOT
SELECT studentno, studentresult
FROM result
WHERE NOT studentno = 1000

3、模糊查询

操作符名称 语法 描述
IS NULL a IS NULL 若操作符为 NULL,则结果为真
IS NOT NULL a IS NOT NULL 若操作符不为 NULL,则结果为真
BETWEEN a BETWEEN b AND c 若 a 范围在 b 与 c 之间,则结果为真
LIKE a LIKE b SQL 模式匹配,若a 匹配 b,则结果为真
IN a IN(a1, a2, a3, ……) 若 a 等于 a1,a2…… 中的某一个,则结果为真

注意:

  • 数据数据类型的记录之间才能进行算术运算
  • 相同数据类型的数据之间才能进行比较
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
-- =============================================
-- LIKE
-- =============================================

-- 查询姓刘的同学的学号及姓名
SELECT studentno,studentname
FROM student
WHERE studentname LIKE '张%'

-- 查询姓刘且姓名两个字的同学
SELECT studentno,studentname
FROM student
WHERE studentname LIKE '张_'

-- 查询姓刘且姓名三个字的同学
SELECT studentno,studentname
FROM student
WHERE studentname LIKE '张__'

-- 查询姓名中含有嘉字的同学
SELECT studentno,studentname
FROM student
WHERE studentname LIKE '%嘉%'

-- =============================================
-- IN
-- =============================================

-- 查询学号为1001,1002,1003的学生姓名
SELECT studentname FROM student
WHERE studentno IN (1001, 1002, 1003)

-- 查询地址在北京,南京,河南洛阳的学生
SELECT studentno, studentname, address
FROM student
WHERE address IN('北京', '南京', '河南洛阳')

-- =============================================
-- NULL
-- =============================================

-- 查询出生日期没有填写的同学
-- 不能直接写=NULL,一定要用IS NULL
SELECT studentno,studentname
FROM student
WHERE `borndate` IS NULL

-- 查询出生日期填写的同学
SELECT studentname, borndate FROM student
WHERE borndate IS NOT NULL

-- 查询没有写家庭住址的同学
-- 空字符串不等于NULL
SELECT studentname, address FROM student
WHERE address IS NULL OR address = ''

踩雷

  • 没有 a = NULL这种写法,必须写成 a IS NULL
  • 空字符串即**``**,并不等于 NULL

4、连接查询

Join

操作符名称 描述
INNER JOIN 如果左右表都匹配到,则返回行
LEFT JOIN 即使右表中没有匹配,也从左表中返回所有的行
RIGHT JOIN 即使左边中没有匹配,也从右表中返回所有的行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
/*
连接查询
如果需要多张数据表的数据进行查询,则可以通过连接运算符实现多个查询

内连接 INNER JOIN
查询两个表中的结果集中的交集

外连接 OUTER JOIN
左外连接 LEFT JOIN
以左表为基准,右表来一一匹配,匹配不上的,返回左表的记录,右表以NULL填充
右外连接 RIGHT JOIN
以右表为基准,左表来一一匹配,匹配不上的,返回右表的记录,左表以NULL填充
*/

-- 查询参加了考试的同学信息(学号,学生姓名,科目编号,分数)
-- INNER JOIN
SELECT s.studentno, studentname, subjectno, studentresult
FROM student s
INNER JOIN result r
ON s.`studentno` = r.`studentno`

-- RIGHT JOIN
SELECT s.studentno, studentname, subjectno, studentresult
FROM student s
RIGHT JOIN result r
ON s.`studentno` = r.`studentno`

-- LEFT JOIN
-- 此时会出现问题,会将存在于student中但没有参加考试的学生也一并打印
-- 可以使用where进一步筛选
SELECT s.studentno, studentname, subjectno, studentresult
FROM student s
LEFT JOIN result r
ON s.`studentno` = r.`studentno`
WHERE r.`studentresult` IS NOT NULL

-- LEFT JOIN
-- 将左表更换为result即可,重点是参加考试的同学
SELECT s.studentno, studentname, subjectno, studentresult
FROM result r
LEFT JOIN student s
ON s.`studentno` = r.`studentno`

-- 查询缺考的同学
-- LEFT JOIN
SELECT s.studentno, studentname, subjectno, studentresult
FROM student s
LEFT JOIN result r
ON s.`studentno` = r.`studentno`
WHERE r.`studentresult` IS NULL

-- INNER JOIN
-- 需要双方都匹配到,而缺考并没有记录在成绩表中
SELECT s.studentno, studentname, subjectno, studentresult
FROM student s
INNER JOIN result r
ON s.`studentno` = r.`studentno`
WHERE r.`studentresult` IS NULL

-- RIGHT JOIN
-- 同上,缺考没有记录在成绩表中
SELECT s.studentno, studentname, subjectno, studentresult
FROM student s
RIGHT JOIN result r
ON s.`studentno` = r.`studentno`
WHERE r.`studentresult` IS NULL

-- 查询参加了考试的同学信息(学号,学生姓名,科目名,分数)
-- 第一次查询以成绩表为主,查询结果作为左表,使用RIGHT JOIN
-- 第二次查询以左表(第一次的查询结果)为主,使用LEFT JOIN
-- 当然,两次都是用INNER JOIN也是没问题的
SELECT s.studentno, studentname, subjectname, studentresult
FROM student s
RIGHT JOIN result r
ON s.`studentno` = r.`studentno`
LEFT JOIN `subject` u
ON r.`subjectno` = u.subjectno

加强练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
-- 查询参加了考试的同学信息(学号,学生姓名,科目名,分数)
SELECT r.studentno, studentname, subjectname, studentresult
FROM result r
LEFT JOIN student s
ON r.studentno = s.studentno
LEFT JOIN `subject` u
ON r.subjectno = u.subjectno

-- 查询学员及所属的年级(学号,学生姓名,年级名)
SELECT studentno, studentname, gradename
FROM student s
LEFT JOIN grade g
ON s.gradeid = g.gradeid

-- 查询科目及所属的年级(科目名称,年级名称)
SELECT subjectname, gradename
FROM `subject` s
LEFT JOIN grade g
ON s.gradeid = g.gradeid

-- 查询 C语言-2 的所有考试结果(学号 学生姓名 科目名称 成绩)
SELECT t.studentno, studentname, subjectname, studentresult
FROM `result` r
LEFT JOIN `subject` s
ON s.subjectno = r.subjectno
LEFT JOIN student t
ON t.studentno = r.studentno
WHERE s.subjectname = 'C语言-2'

WHERE 不能写在两个 JOIN 语句之间,要放在后面!详情请看这节开头的 SELECT 语法

5、自查询

顾名思义,即数据表与自身进行连接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-- 创建一个表
CREATE TABLE `category` (
`categoryid` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主题id',
`pid` INT(10) NOT NULL COMMENT '父id',
`categoryName` VARCHAR(50) NOT NULL COMMENT '主题名字',
PRIMARY KEY (`categoryid`)
) ENGINE=INNODB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8

-- 插入数据
INSERT INTO `category` (`categoryid`, `pid`, `categoryName`)
VALUES('2','1','信息技术'),
('3','1','软件开发'),
('4','3','数据库'),
('5','1','美术设计'),
('6','3','web开发'),
('7','5','ps技术'),
('8','2','办公信息');
1
2
3
4
5
-- 编写SQL语句,将栏目的父子关系呈现出来 (父栏目名称,子栏目名称)
-- 核心思想:把一张表看成两张一模一样的表,然后将这两张表连接查询(自连接)
SELECT a.categoryName AS '父栏目',b.categoryName AS '子栏目'
FROM category AS a,category AS b
WHERE a.`categoryid`=b.`pid`

6、排序和分页

ORDER BY

  • 用于根据指定的列对结果集进行排序
  • 默认按照 ASC 升序进行排序
  • 如果需要进行降序排序,可以使用 DESC 关键字

LIMIT

1
语法:SELECT * FROM table LIMIT [offset,] rows | rows OFFSET offset
  • 第一个参数指定第一个返回记录行的偏移量,注意从0开始
  • 第二个参数指定返回记录行的最大数目
  • 如果只给定一个参数:它表示返回最大的记录行数目
  • 第二个参数为 -1 表示检索从某一个偏移量到记录集的结束所有的记录行
  • 初始记录行的偏移量是 0(而不是 1)

分页优点:

  • 用户体验
  • 网络传输
  • 查询压力
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
-- 查询 数据库结构-1 的所有考试结果(学号 学生姓名 科目名称 成绩)
-- 注意是 LEFT JOIN 而不是 JOIN LEFT
SELECT s.studentno, studentname, subjectname, studentresult
FROM result r
LEFT JOIN student s
ON r.`studentno` = s.studentno
LEFT JOIN `subject` u
ON r.`subjectno` = u.subjectno
WHERE subjectname = 'C语言-2'

-- 按成绩升序排序
-- ORDER BY 默认就是升序
SELECT s.studentno, studentname, subjectname, studentresult
FROM result r
LEFT JOIN student s
ON r.`studentno` = s.studentno
LEFT JOIN `subject` u
ON r.`subjectno` = u.subjectno
WHERE subjectname = 'C语言-2'
ORDER BY studentresult ASC

-- 每页显示5条数据
SELECT s.studentno, studentname, subjectname, studentresult
FROM result r
LEFT JOIN student s
ON r.`studentno` = s.studentno
LEFT JOIN `subject` u
ON r.`subjectno` = u.subjectno
WHERE subjectname = 'C语言-2'
ORDER BY studentresult ASC
LIMIT 0, 5

-- 查询 JAVA第一学年 课程成绩前10名并且分数大于80的学生信息(学号,姓名,课程名,分数)
SELECT s.studentno, studentname, subjectname, studentresult
FROM result r
LEFT JOIN student s
ON s.`studentno` = r.`studentno`
LEFT JOIN `subject` u
ON u.`subjectno` = r.`subjectno`
WHERE subjectname = 'Java程序设计-1' AND studentresult > 80
ORDER BY studentresult DESC
LIMIT 0, 10

7、子查询

子查询就是在查询语句的 WHERE 条件子句中,又嵌套了另一个查询语句,嵌套查询可由多个子查询组成,求解的方式是由里及外。子查询返回的结果一般都是集合,故而建议使用 IN 关键字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
-- 查询 Java程序设计-1 的所有考试结果(学号,科目编号,成绩),并且成绩降序排列
-- 连接查询
SELECT studentno, r.subjectno, studentresult
FROM result r
LEFT JOIN `subject` s
ON s.`subjectno` = r.`subjectno`
WHERE s.`subjectname` = 'Java程序设计-1'

-- 子查询
SELECT studentno, subjectno, studentresult
FROM result
WHERE subjectno = (SELECT subjectno FROM `subject` WHERE subjectname = 'Java程序设计-1')

-- 查询课程为 Java程序设计-1 且分数不小于90分的学生的学号和姓名
-- 连接查询
SELECT r.studentno, studentname
FROM result r
LEFT JOIN `subject` s
ON r.`subjectno` = s.`subjectno`
LEFT JOIN student stu
ON r.`studentno` = stu.studentno
WHERE subjectname = 'Java程序设计-1' AND studentresult >= 90

-- 子查询
-- 由于分数不小于90的同学是一个集合(数量大于1),因此在外层嵌套中需要使用IN
SELECT studentno, studentname FROM student WHERE studentno IN (
SELECT studentno FROM result WHERE subjectno = (
SELECT subjectno FROM `subject` WHERE subjectname = 'Java程序设计-1')
AND studentresult >= 90)

-- 连接查询 + 子查询
SELECT r.studentno, studentname
FROM result r
LEFT JOIN `student` s
ON r.`studentno` = s.`studentno`
WHERE subjectno = (SELECT subjectno FROM `subject` WHERE subjectname = 'Java程序设计-1') AND studentresult >= 90

-- 查Java程序设计-1 的前5名学生的成绩信息(学号,姓名,分数)
-- 连接查询
SELECT s.studentno, studentname, studentresult
FROM student s
RIGHT JOIN result r
ON s.studentno = r.studentno
LEFT JOIN `subject` sub
ON r.`subjectno` = sub.subjectno
WHERE subjectname = 'Java程序设计-1'
ORDER BY studentresult DESC
LIMIT 0,5

-- 查询张伟同学所在的年级名称
SELECT studentname, gradename
FROM student s
LEFT JOIN grade g
ON s.`gradeid` = g.gradeid
WHERE studentname = '张伟'

8、分组查询

1
2
3
4
5
6
7
 -- 查询不同课程的课程编号,课程名称,平均分(大于60),最高分,最低分
SELECT r.subjectno 课程编号, subjectname 课程名称, MAX(studentresult) 最高分, MIN(studentresult) 最低分, AVG(studentresult) 平均分
FROM result r
INNER JOIN `subject` s
ON r.subjectno = s.subjectno
GROUP BY 课程编号
HAVING 平均分 > 60

WHERE 必须写在 GROUP BY 前面,分组后筛选要使用 HAVING,因为 HAVING 是根据前面筛选的字段再筛选,而 WHERE 是根据数据表中的字段直接进行筛选。

七、MySQL 函数

1、没那么常用的常用函数

数据函数

1
2
3
4
5
SELECT ABS(-8);  /*绝对值*/
SELECT CEILING(9.4); /*向上取整*/
SELECT FLOOR(9.4); /*向下取整*/
SELECT RAND(); /*随机数,返回一个0-1之间的随机数*/
SELECT SIGN(0); /*符号函数: 负数返回-1,正数返回1,0返回0*/

字符串函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
SELECT CHAR_LENGTH('狂神说坚持就能成功'); /*返回字符串包含的字符数*/
SELECT CONCAT('我','爱','程序'); /*合并字符串,参数可以有多个*/
SELECT INSERT('我爱编程helloworld',1,2,'超级热爱'); /*替换字符串,从某个位置开始替换某个长度*/
SELECT LOWER('KuangShen'); /*小写*/
SELECT UPPER('KuangShen'); /*大写*/
SELECT LEFT('hello,world',5); /*从左边截取*/
SELECT RIGHT('hello,world',5); /*从右边截取*/
SELECT REPLACE('狂神说坚持就能成功','坚持','努力'); /*替换字符串*/
SELECT SUBSTR('狂神说坚持就能成功',4,6); /*截取字符串,开始和长度*/
SELECT REVERSE('狂神说坚持就能成功'); /*反转*/

-- 查询姓周的同学,改成邹
SELECT REPLACE(studentname,'周','邹') AS 新名字
FROM student WHERE studentname LIKE '周%';

日期和时间函数

1
2
3
4
5
6
7
8
9
10
11
12
13
SELECT CURRENT_DATE();   /*获取当前日期*/
SELECT CURDATE(); /*获取当前日期*/
SELECT NOW(); /*获取当前日期和时间*/
SELECT LOCALTIME(); /*获取当前日期和时间*/
SELECT SYSDATE(); /*获取当前日期和时间*/

-- 获取年月日,时分秒
SELECT YEAR(NOW());
SELECT MONTH(NOW());
SELECT DAY(NOW());
SELECT HOUR(NOW());
SELECT MINUTE(NOW());
SELECT SECOND(NOW());

系统信息函数

1
2
SELECT VERSION();  /*版本*/
SELECT USER(); /*用户*/

2、聚合函数

函数名称 描述
COUNT() 返回满足Select条件的记录总和数,如 select count(*)
SUM() 返回数字字段或表达式列作统计,返回一列的总和。
AVG() 通常为数值字段或表达列作统计,返回一列的平均值
MAX() 可以为数值字段,字符字段或表达式列作统计,返回最大的值。
MIN() 可以为数值字段,字符字段或表达式列作统计,返回最小的值。

COUNT

以下三种统计表行数的方法,返回的结果都是一致的,那么有什么区别呢?

1
2
3
SELECT COUNT(studentno) FROM student
SELECT COUNT(1) FROM student
SELECT COUNT(*) FROM student
  • COUNT(ColumnName)
    • 查询某一字段的记录数,会忽略掉为 NULL 的记录。
    • 如果上面查询的是 sex 列,就会发现记录数只有两条。
  • COUNT(*)
    • 因为是 SQL92 定义的标准统计行数的语法,因此 MySQL 对他进行了很多优化。
    • MyISAM 会将表的总行数单独记录下来供 COUNT(*) 查询。
    • InnoDB 会在扫表的时候选择最小的索引来降低成本。
    • 上述两个引擎优化的前提是没有进行 WHERE 和 GROUP 的条件查询

在 InnoDB 中 COUNT(*)COUNT(1) 实现上没有区别,而且效率一样,但是 COUNT(字段) 需要进行字段的非 NULL 判断,所以效率会低一些。

因为 COUNT(*) 是 SQL92 定义的标准统计行数的语法,并且效率高,所以请直接使用 COUNT(*) 查询表的行数!

练习

1
2
3
4
5
6
7
8
-- 总和
SELECT SUM(StudentResult) AS 总和 FROM result;
-- 平均分
SELECT AVG(StudentResult) AS 平均分 FROM result;
-- 最高分
SELECT MAX(StudentResult) AS 最高分 FROM result;
-- 最低分
SELECT MIN(StudentResult) AS 最低分 FROM result;

3、MD5加密

MD5即Message-Digest Algorithm 5(信息-摘要算法5),用于确保信息传输完整一致。是计算机广泛使用的杂凑算法之一(又译摘要算法、哈希算法),主流编程语言普遍已有MD5实现。将数据(如汉字)运算为另一固定长度值,是杂凑算法的基础原理,MD5的前身有MD2、MD3和MD4。

实现数据加密

新建一个测试表

1
2
3
4
5
6
CREATE TABLE `testmd5` (
`id` INT(4) NOT NULL,
`name` VARCHAR(20) NOT NULL,
`pwd` VARCHAR(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8

插入数据

1
INSERT INTO testmd5 VALUES(1,'yqx','123456'),(2,'yqx','456789')

加密

1
update testmd5 set pwd = md5(pwd);

或者在插入数据时加密

1
INSERT INTO testmd5 VALUES(3,'yqx3',md5('123456'));

用户登录(将用户输入的密码加密后与数据库的密码进行比对)

1
SELECT * FROM testmd5 WHERE `name`='yqx' AND pwd=MD5('123456');

4、小结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
-- ================ 内置函数 ================
-- 数值函数
abs(x) -- 绝对值 abs(-10.9) = 10
format(x, d) -- 格式化千分位数值 format(1234567.456, 2) = 1,234,567.46
ceil(x) -- 向上取整 ceil(10.1) = 11
floor(x) -- 向下取整 floor (10.1) = 10
round(x) -- 四舍五入去整
mod(m, n) -- m%n m mod n 求余 10%3=1
pi() -- 获得圆周率
pow(m, n) -- m^n
sqrt(x) -- 算术平方根
rand() -- 随机数
truncate(x, d) -- 截取d位小数

-- 时间日期函数
now(), current_timestamp(); -- 当前日期时间
current_date(); -- 当前日期
current_time(); -- 当前时间
date('yyyy-mm-dd hh:ii:ss'); -- 获取日期部分
time('yyyy-mm-dd hh:ii:ss'); -- 获取时间部分
date_format('yyyy-mm-dd hh:ii:ss', '%d %y %a %d %m %b %j'); -- 格式化时间
unix_timestamp(); -- 获得unix时间戳
from_unixtime(); -- 从时间戳获得时间

-- 字符串函数
length(string) -- string长度,字节
char_length(string) -- string的字符个数
substring(str, position [,length]) -- 从str的position开始,取length个字符
replace(str ,search_str ,replace_str) -- 在str中用replace_str替换search_str
instr(string ,substring) -- 返回substring首次在string中出现的位置
concat(string [,...]) -- 连接字串
charset(str) -- 返回字串字符集
lcase(string) -- 转换成小写
left(string, length) -- 从string2中的左边起取length个字符
load_file(file_name) -- 从文件读取内容
locate(substring, string [,start_position]) -- 同instr,但可指定开始位置
lpad(string, length, pad) -- 重复用pad加在string开头,直到字串长度为length
ltrim(string) -- 去除前端空格
repeat(string, count) -- 重复count次
rpad(string, length, pad) --在str后用pad补充,直到长度为length
rtrim(string) -- 去除后端空格
strcmp(string1 ,string2) -- 逐字符比较两字串大小

-- 聚合函数
count()
sum();
max();
min();
avg();
group_concat()

-- 其他常用函数
md5();
default();

八、事务(transaction)

1、ACID 原则

原子性(Atomicity)

原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。

一致性(Consistency)

一致性是指事务前后数据的完整性必须保持一致。

隔离性(Isolation)

事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事物的操作数据所干扰,多个并发事物之间要相互隔离。

持久性(Durability)

持久性是指一个事物一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。

接下来举例进行说明。

原子性

针对同一个事务

在个过程包含两个步骤

A:800 - 200 = 600

B:200 + 200 = 400

原子性表示,这两个步骤要么一起成功,要么一起失败,不可能 A 的钱扣掉了,而 B 却没有收到钱。

一致性

针对一个事物操作前与操作后的状态一致

操作前A:800,B:200
操作后A:600,B:400

一致性表示事务完成后,符合逻辑运算(A + B = 1000)

持久性

表示事务结束后的数据不随着外界原因导致数据丢失

操作前A:800,B:200
操作后A:600,B:400
如果在操作前(事务还没有提交)服务器宕机或者断电,那么重启数据库以后,数据状态应该为
A:800,B:200
如果在操作后(事务已经提交)服务器宕机或者断电,那么重启数据库以后,数据状态应该为
A:600,B:400

隔离性

针对多个用户同时操作,主要是排除其他事务对本次事务的影响

事务一)C向B转账100
事务二)A向B转账200

两个事务同时进行,其中一个事务读取到另外一个事务还没有提交的数据。

例如 C 先给 B 转100元,在事务还没有提交之前,A 给 B 转了200元,而此时数据库中 B 的金额仍然是 200 元。待 C 给 B 的事务完成后,A 也完成了他的事务,此时数据库中 B 的金额将会是后手事务二提交的,也就是200 + 200 = 400,直接少了100元。

隔离性用于解决以上问题。

2、隔离级别

脏读:

指一个事务读取到另一个事务未提交的数据。(具体查看上面的隔离性)

不可重复读:

在一个事务内读取表中的某一行数据,多次读取结果不同。(这个不一定是错误,只是某些场合不对)

页面统计查询值

生成报表时,有人又转给 B 300 元

虚读:

是指在一个事务内读取到了别的事务插入的数据,导致前后读取数量总量不一致。
(一般是行影响,如下图所示:多了一行)

四种隔离级别设置

数据库

set transaction isolation level 设置事务隔离级别
select @@tx_isolation 查询当前事务隔离级别

设置 描述
Serializable 可避免脏读、不可重复读、虚读的发生。(串行化)
Repeatable read 可避免脏读、不可重复读情况的发生。(可重复读)
Repeatable read 可避免脏读情况发生。(读已提交)
Read uncommitted 最低级别,以上情况均无法保证。(读未提交)

java

适当的 Connection 方法,比如 setAutoCommit 或 setTransactionIsolation

设置 描述
TRANSACTION_SERIALIZABLE 指示不可以发生脏读、不可重复读和虚读的常量。
TRANSACTION_REPEATABLE_READ 指示不可以发生脏读和不可重复读的常量;虚读可以发生。
TRANSACTION_READ_UNCOMMITTED 指示可以发生脏读 (dirty read)、不可重复读和虚读 (phantom read) 的常量。
TRANSACTION_READ_COMMITTED 指示不可以发生脏读的常量;不可重复读和虚读可以发生。

3、基本语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
-- 使用set语句来改变事务自动提交
SET autocommit = 0 -- 关闭事务
SET autocommit = 1 -- 开启事务

-- 注意:
-- 1. MySQL中默认是自动提交
-- 2. 使用事务时应先关闭自动提交

-- 开启一个事务,标记事务的起始点
START TRANSACTION

-- 提交事务到数据库
COMMIT

-- 将事务回滚,数据回到本次事务的初始状态
ROLLBACK

-- 还原MySQL数据库的自动提交
SET autocommit = 1

-- 保存点
SAVEPOINT pointname -- 设置一个事务保存点
ROLLBACK TO SAVEPOINT pointname -- 回滚到保存点
RELEASE SAVEPOINT pointname -- 删除保存点

4、测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/*
测试题目

A在线购买一款价格为500元商品,网上银行转账
A的银行卡余额为2000元,然后支付给B商家500元
商家B一开始的银行卡余额为10000

*/

-- 创建数据库shop和创建表account并插入2条数据
CREATE DATABASE shop CHARACTER SET utf8 COLLATE utf8_general_ci;
USE shop;

CREATE TABLE account(
id INT(11) AUTO_INCREMENT,
`name` VARCHAR(50) NOT NULL,
cash DECIMAL(9,2) NOT NULL,
PRIMARY KEY(id)
)ENGINE=INNODB DEFAULT CHARSET = utf8;

INSERT INTO account(`name`, cash) VALUES('A', 2000),('B', 10000);

-- 关闭自动提交事务
SET autocommit = 0;

-- 开始事务
START TRANSACTION;

-- 事务
UPDATE account SET cash = cash - 500 WHERE `name` = 'A'; -- A支付给B 500元
UPDATE account SET cash = cash + 500 WHERE `name` = 'B'; -- B收到A的 500元

-- 提交事务
COMMIT;

-- 事务回滚(事务提交失败时执行,成功则不会发生回滚)
ROLLBACK;

-- 开启自动提交事务
SET autocommit = 1;

九、索引

1、介绍

一般的应用系统,读写比例在10:1左右,而且插入操作和一般的更新操作很少出现性能问题,在生产环境中,我们遇到最多的,也是最容易出问题的,还是一些复杂的查询操作,因此对查询语句的优化显然是重中之重。说起加速查询,就不得不提到索引了。

索引在MySQL中也叫做“键”,是存储引擎用于快速找到记录的一种数据结构。索引对于良好的性能非常关键,尤其是当表中的数据量越来越大时,索引对于性能的影响愈发重要。
索引优化应该是对查询性能优化最有效的手段了。索引能够轻易将查询性能提高好几个数量级。

索引相当于字典的音序表,如果要查某个字,如果不使用音序表,则需要从几百页中逐页去查。

索引的作用

  • 提高查询速度
  • 确保数据的唯一性
  • 可以加速表和表之间的连接 , 实现表与表之间的参照完整性
  • 使用分组和排序子句进行数据检索时 , 可以显著减少分组和排序的时间
  • 全文检索字段进行搜索优化.

分类

  • 主键索引 (Primary Key)
  • 唯一索引 (Unique)
  • 常规索引 (Index)
  • 全文索引 (FullText)

2、各索引区别

主键索引

  • 最常见的索引类型
  • 确保数据记录的唯一性
  • 确定特定数据记录在数据库中的位置
  • 一张表只能有一个主键索引

不唯一的唯一索引

  • 避免同一个表中某数据列的值重复
  • 一张表可以有多个唯一索引

常规索引

  • 快速定位特定数据
  • index 和 key 关键字都可以设置常规索引
  • 应加在需要查询的字段
  • 不宜添加太多常规索引,会影响数据增删改的效率,因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件。

全文索引

  • 百度搜索

  • 快速定位特定数据

  • 只能用于 MyISAM 类型的数据表(INNODB 在 MySQL 5.6 之后也支持全文索引)

  • 只能用于 CHAR,VARCHAR,TEXT 等文本类型数据

  • 适合大型数据集

建立索引会占用磁盘空间的索引文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
-- 方法一:建表时
CREATE TABLE 表名 (
字段名1 数据类型 [完整性约束条件…],
字段名2 数据类型 [完整性约束条件…],
[UNIQUE | FULLTEXT | SPATIAL ] INDEX | KEY
[索引名] (字段名[(长度)] [ASC |DESC])
);

-- 方法二:建表后创建索引
CREATE [UNIQUE | FULLTEXT | SPATIAL ] INDEX 索引名 ON 表名 (字段名[(长度)] [ASC |DESC]) ;

-- 方法三:建表后修改索引
ALTER TABLE 表名 ADD [UNIQUE | FULLTEXT | SPATIAL ] INDEX 索引名 (字段名[(长度)] [ASC |DESC]) ;

-- 删除索引
DROP INDEX 索引名 ON 表名字;

-- 删除主键索引
ALTER TABLE 表名 DROP PRIMARY KEY;

-- 显示索引信息
SHOW INDEX FROM 表名

-- 分析SQL语句执行性能
EXPLAIN SELECT * FROM student WHERE studentno='1000';

-- 使用全文索引
-- 全文索引通过MATCH()函数完成
-- 搜索字符串作为AGAINST()的参数被给定。
-- 搜索以忽略字母大小写的方式执行。
-- 对于表中的每个记录行,MATHCH()会返回一个相关性值。即在搜索字符串与MATCH()参数列表中指定的列的文本之间的相似性尺度。
EXPLAIN SELECT * FROM student WHERE studentno='1000';

3、测试各索引性能

建立测试表

1
2
3
4
5
6
7
8
9
10
11
12
CREATE TABLE `app_user` (
`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) DEFAULT '' COMMENT '用户昵称',
`email` VARCHAR(50) NOT NULL COMMENT '用户邮箱',
`phone` VARCHAR(20) DEFAULT '' COMMENT '手机号',
`gender` TINYINT(4) UNSIGNED DEFAULT 0 COMMENT '性别(0:男;1:女)',
`password` VARCHAR(100) NOT NULL COMMENT '密码',
`age` TINYINT(4) DEFAULT 0 COMMENT '年龄',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
`update_time` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COMMENT='app用户表'

插入100W 测试数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
DROP FUNCTION IF EXISTS mock_data;
DELIMITER $$
CREATE FUNCTION mock_data()
RETURNS INT
BEGIN
DECLARE num INT DEFAULT 1000000;
DECLARE i INT DEFAULT 0;
WHILE i < num DO
INSERT INTO app_user(`name`, `email`, `phone`, `gender`, `password`, `age`)
VALUES(CONCAT('用户', i), '24736743@qq.com', CONCAT('18', FLOOR(RAND()*(999999999-100000000)+100000000)),FLOOR(RAND()*2),UUID(), FLOOR(RAND()*100));
SET i = i + 1;
END WHILE;
RETURN i;
END;
SELECT mock_data();

测试结果

每个索引效率都差不多,会比没有索引快十倍,创建索引的时候会耗点时间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- 无索引
SELECT * FROM app_user WHERE NAME = '用户1000000' -- 4.564 sec
SELECT * FROM app_user WHERE phone = '18586428336' -- 4.316 sec

-- 主键索引
SELECT * FROM app_user WHERE id = 2878891 -- 0.392 sec

-- 唯一索引
-- 先添加唯一索引,建立索引比较费时
ALTER TABLE app_user ADD UNIQUE INDEX uidx(`name`) -- 23.014 sec
SELECT * FROM app_user WHERE `name` = '用户1234567' -- 0.341 sec

-- 常规索引
ALTER TABLE app_user ADD INDEX idx(`phone`) -- 24.027 sec
SELECT * FROM app_user WHERE phone = '18586428336' -- 0.408 sec

4、小结

索引准则

  • 索引不是越多越好
  • 不要对经常变动的数据加索引
  • 小数据量的表建议不要加索引
  • 索引一般应加在查找条件的字段

索引的数据结构

我们在创建上述索引的时候,可以为其指定索引类型

  • hash 类型的索引:查询单条快,范围查询慢
  • btree 类型的索引:b+ 数,层数越多,数据量呈指数级增长(INNODB 默认)

不同的存储引擎支持的索引类型也不一样

  • INNODB 支持事务,支持行级别锁定,支持 B-tree、Full-Text 等索引,不支持 Hash 索引。
  • MyISAM 不支持事务,支持表级别锁定,支持 B-tree、Full-Text等索引,不支持 Hash 索引。
  • Memory 不支持事务,支持表级别锁定,支持B-tree、Hash 等索引,不支持 Full-Text 索引。
  • NDB 支持事务,支持行级别锁定,支持 Hash 索引,不支持 B-tree、Full-Text 等索引。
  • Archive 不支持事务,支持表级别锁定,不支持 B-tree、Hash、Full-text 等索引。

参考文章:CodingLabs - MySQL索引背后的数据结构及算法原理

十、权限管理和备份

1、权限管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/* 用户和权限管理 */
用户信息表:mysql.user

-- 刷新权限
FLUSH PRIVILEGES

-- 增加用户 CREATE USER kuangshen IDENTIFIED BY '123456'
CREATE USER 用户名 IDENTIFIED BY [PASSWORD] 密码(字符串)
- 必须拥有mysql数据库的全局CREATE USER权限,或拥有INSERT权限。
- 只能创建用户,不能赋予权限。
- 用户名,注意引号:如 'user_name'@'192.168.1.1'
- 密码也需引号,纯数字密码也要加引号
- 要在纯文本中指定密码,需忽略PASSWORD关键词。要把密码指定为由PASSWORD()函数返回的混编值,需包含关键字PASSWORD

-- 重命名用户 RENAME USER kuangshen TO kuangshen2
RENAME USER old_user TO new_user

-- 设置密码
SET PASSWORD = PASSWORD('密码') -- 为当前用户设置密码
SET PASSWORD FOR 用户名 = PASSWORD('密码') -- 为指定用户设置密码

-- 删除用户 DROP USER kuangshen2
DROP USER 用户名

-- 分配权限/添加用户
GRANT 权限列表 ON 表名 TO 用户名 [IDENTIFIED BY [PASSWORD] 'password']
- all privileges 表示所有权限
- *.* 表示所有库的所有表
- 库名.表名 表示某库下面的某表

-- 查看权限 SHOW GRANTS FOR root@localhost;
SHOW GRANTS FOR 用户名

-- 查看当前用户权限
SHOW GRANTS;
SHOW GRANTS FOR CURRENT_USER;
SHOW GRANTS FOR CURRENT_USER();

-- 撤消权限
REVOKE 权限列表 ON 表名 FROM 用户名
REVOKE ALL PRIVILEGES, GRANT OPTION FROM 用户名 -- 撤销所有权限

权限解释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
-- 权限列表
ALL [PRIVILEGES] -- 设置除GRANT OPTION之外的所有简单权限
ALTER -- 允许使用ALTER TABLE
ALTER ROUTINE -- 更改或取消已存储的子程序
CREATE -- 允许使用CREATE TABLE
CREATE ROUTINE -- 创建已存储的子程序
CREATE TEMPORARY TABLES -- 允许使用CREATE TEMPORARY TABLE
CREATE USER -- 允许使用CREATE USER, DROP USER, RENAME USER和REVOKE ALL PRIVILEGES。
CREATE VIEW -- 允许使用CREATE VIEW
DELETE -- 允许使用DELETE
DROP -- 允许使用DROP TABLE
EXECUTE -- 允许用户运行已存储的子程序
FILE -- 允许使用SELECT...INTO OUTFILE和LOAD DATA INFILE
INDEX -- 允许使用CREATE INDEX和DROP INDEX
INSERT -- 允许使用INSERT
LOCK TABLES -- 允许对您拥有SELECT权限的表使用LOCK TABLES
PROCESS -- 允许使用SHOW FULL PROCESSLIST
REFERENCES -- 未被实施
RELOAD -- 允许使用FLUSH
REPLICATION CLIENT -- 允许用户询问从属服务器或主服务器的地址
REPLICATION SLAVE -- 用于复制型从属服务器(从主服务器中读取二进制日志事件)
SELECT -- 允许使用SELECT
SHOW DATABASES -- 显示所有数据库
SHOW VIEW -- 允许使用SHOW CREATE VIEW
SHUTDOWN -- 允许使用mysqladmin shutdown
SUPER -- 允许使用CHANGE MASTER, KILL, PURGE MASTER LOGS和SET GLOBAL语句,mysqladmin debug命令;允许您连接(一次),即使已达到max_connections。
UPDATE -- 允许使用UPDATE
USAGE -- “无权限”的同义词
GRANT OPTION -- 允许授予权限


/* 表维护 */

-- 分析和存储表的关键字分布
ANALYZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE 表名 ...
-- 检查一个或多个表是否有错误
CHECK TABLE tbl_name [, tbl_name] ... [option] ...
option = {QUICK | FAST | MEDIUM | EXTENDED | CHANGED}
-- 整理数据文件的碎片
OPTIMIZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name]

2、MySQL 备份

数据备份的重要性

  • 保证重要数据不丢失
  • 数据转移

MySQL 数据库备份方法

  • 数据库管理工具,如 SQLyog
  • 直接拷贝数据库物理文件(/data/*)
  • 使用命令行,mysqldump
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
-- 导出
1. 导出一张表
-- mysqldump -uroot -p123456 school student >D:/a.sql
  mysqldump -u用户名 -p密码 库名 表名 > 文件名(D:/a.sql)
  
2. 导出多张表
-- mysqldump -uroot -p123456 school student result >D:/a.sql
  mysqldump -u用户名 -p密码 库名 表123 > 文件名(D:/a.sql)
  
3. 导出所有表
-- mysqldump -uroot -p123456 school >D:/a.sql
  mysqldump -u用户名 -p密码 库名 > 文件名(D:/a.sql)
  
4. 导出一个库
-- mysqldump -uroot -p123456 -B school >D:/a.sql
  mysqldump -u用户名 -p密码 -B 库名 > 文件名(D:/a.sql)

可以-w携带备份条件

-- 导入
1. 在登录mysql的情况下:
-- source D:/a.sql
  source 备份文件
  
2. 在不登录的情况下
-- mysqldump -uroot -p123456 school <D:/a.sql
  mysql -u用户名 -p密码 库名 < 备份文件

十一、规范化数据库设计

1、为什么需要数据库设计

当数据库比较复杂时我们需要设计数据库

糟糕的数据库设计 :

  • 数据冗余,存储空间浪费
  • 数据更新和插入的异常
  • 程序性能差

良好的数据库设计 :

  • 节省数据的存储空间
  • 能够保证数据的完整性
  • 方便进行数据库应用系统的开发

软件项目开发周期中数据库设计 :

  • 需求分析阶段:分析客户的业务和数据处理需求。
  • 概要设计阶段:设计数据库的E-R模型图 , 确认需求信息的正确和完整。

设计数据库步骤

  • 收集信息,与该系统有关人员进行交流 , 座谈 , 充分了解用户需求 , 理解数据库需要完成的任务。
  • 标识数据库要管理的关键对象或实体,实体一般是名词。
  • 标识每个实体需要存储的详细信息。
  • 标识实体之间的关系。

2、三大范式

问题 : 为什么需要数据规范化?

不合规范的表设计会导致的问题:

  • 信息重复

  • 更新异常

  • 插入异常

    • 无法正确表示信息
  • 删除异常

    • 丢失有效信息

第一范式 (1st NF)

第一范式是最基本的范式。如果数据库表中的所有字段值都是不可分解的原子值,就说明该数据库表满足了第一范式。

第一范式的合理遵循需要根据系统的实际需求来定。比如某些数据库系统中需要用到“地址”这个属性,本来直接将“地址”属性设计成一个数据库表的字段就行。但是如果系统经常会访问“地址”属性中的“城市”部分,那么就非要将“地址”这个属性重新拆分为省份、城市、详细地址等多个部分进行存储,这样在对地址中某一部分操作的时候将非常方便。这样设计才算满足了数据库的第一范式,如下表所示。

第二范式(2nd NF)

第二范式在第一范式的基础之上更进一层。第二范式需要确保数据库表中的每一列都和主键相关,而不能只与主键的某一部分相关(主要针对联合主键而言)。也就是说在一个数据库表中,一个表中只能保存一种数据,不可以把多种数据保存在同一张数据库表中。

比如要设计一个订单信息表,因为订单中可能会有多种商品,所以要将订单编号和商品编号作为数据库表的联合主键,如下表所示。

这样就产生一个问题:这个表中是以订单编号和商品编号作为联合主键。这样在该表中商品名称、单位、商品价格等信息不与该表的主键相关,而仅仅是与商品编号相关。所以在这里违反了第二范式的设计原则。

而如果把这个订单信息表进行拆分,把商品信息分离到另一个表中,把订单项目表也分离到另一个表中,就非常完美了。如下所示。

这样设计,在很大程度上减小了数据库的冗余。如果要获取订单的商品信息,使用商品编号到商品信息表中查询即可。

第三范式(3rd NF)

第三范式需要确保数据表中的每一列数据都和主键直接相关,而不能间接相关

比如在设计一个订单数据表的时候,可以将客户编号作为一个外键和订单表建立相应的关系。而不可以在订单表中添加关于客户其它信息(比如姓名、所属公司等)的字段。如下面这两个表所示的设计就是一个满足第三范式的数据库表。

这样在查询订单信息的时候,就可以使用客户编号来引用客户信息表中的记录,也不必在订单信息表中多次输入客户信息的内容,减小了数据冗余。

但很多时候我们会为了性能而抛弃这些规范化的操作,因为一个复杂的表如果按照三大范式来设计,很有可能会拆成十多个表,这样在数据库查询时会十分影响效率(多表查询)。

规范化和性能的关系

  • 为满足某种商业目标 , 数据库性能比规范化数据库更重要

  • 在数据规范化的同时 , 要综合考虑数据库的性能

  • 通过在给定的表中添加额外的字段,以大量减少需要从中搜索信息所需的时间

  • 通过在给定的表中插入计算列,以方便查询