一、数据库操作

1.查看数据库

show databases;

2.创建数据库

create database if not exists [数据库名] charset UTF8;

  1. 推荐写charset UTF8mb4指定字符集
  2. 以使用反引号包裹关键字作为数据库/列/表的名字
  3. MySQL的UTF8不是完整的UTF8,UTF8mb4是完整的UTF8

3.选中数据库

对于一个数据库服务器,有很多数据库。
需要先选中,再操作。

use 数据库名;

4.删除数据库

drop database 数据库名;

二、数据类型

1.数字数据类型

类型说明
BIT(M)Mbit(M省略则为1)
BOOL1byte
TINYINT1byte
SMALLINT2byte
INT4byte
BIGINT8byte
FLOAT4byte
DOUBLE8byte
DECIMAL(M,D)动态
  1. BIT(M)中(M)填写数字。例如BIT(2)代表占用2bit。
  2. 用DECIMAL可以表示精确数字,但存储空间更大,运算速度更慢。
  3. INT可存储的最大数字为21亿。
  4. DECIMAL不存在精度损失。M是总位数,D是小数点后位数。M最大为65,D最大为30。M默认为10,D默认为0。如果D为零,则没有小数点。

2.字符串类型

类型说明
CHAR(M)0~255个字符(M省略则为1)
VARCHAR(M)M为最大字符长度,具体长度为实际长度
TINYTEXT最大长度为255个字节
TEXT最大长度为65535个字节
MEDIUMTEXT最大长度为2^24-1个字节
LONGTEXT最大长度为2^32-1个字节
  1. 一个字节对应几个字符取决于字符集
  2. ACSII、GBK:一个英文字符占1个字节,一个中文字符占2个字节
  3. UTF-8:一个英文字符占1个字节,一个中文字符占3个字节
  4. Unicode:一个英文字符占2个字节,一个中文字符占2个字节

3.二进制数据

二进制数据指的是图片、视频、音频。

  1. 在实际开发中,这类二进制数据不会存储在数据库中
  2. 一般之间按照文件的方式存储在硬盘中,将文件路径存储在数据库中

4.枚举和集合

5.日期类型

类型说明
DATETIME8Bytes,表示为 YYYY-MM-DD hh:mm:ss

三、数据表操作

1.查看数据库的所有表

show tables;

先使用USE选中数据库,这是进行所有表操作的前提条件。

2.创建数据表

create [temporary] table [if not exists] 表名(列名 类型 [comment "描述"], 列名 类型 [comment "描述"]) [engine 存储引擎] [character set 字符集] [collate 排序规则];

  1. SQL一般使用蛇形命名法,例如student_id。
  2. 使用temporary关键字代表此表不会存储在硬盘中。

3.查看表结构

desc 表名;
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id    | int         | YES  |     | NULL    |       |
| name  | varchar(20) | YES  |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+
  1. Null值为YES代表可以不填。
  2. Key指索引类型。
  3. Default指该列的默认值。
  4. Extra指扩展信息。

4.修改表

alter table 表名 动作 列名 类型;

4.1 添加列

alter table meat add age int after name;
# 在name列后添加一个age列,类型为int
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id    | int         | YES  |     | NULL    |       |
| name  | varchar(20) | YES  |     | NULL    |       |
| age   | int         | YES  |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+

4.2 修改列类型

alter table student modify age bigint;
# 把age类型改为BIGNIT
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id    | int         | YES  |     | NULL    |       |
| name  | varchar(20) | YES  |     | NULL    |       |
| age   | bigint      | YES  |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+

4.3 修改列名

alter table student rename column age to AGE;
# 把age名字改为AGE
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id    | int         | YES  |     | NULL    |       |
| name  | varchar(20) | YES  |     | NULL    |       |
| AGE   | bigint      | YES  |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+

4.4 删除列

alter table student drop AGE;

5.删除表

drop table 表名;

四、增删查改

insert into 表名
select from 表名
update 表名
delete from 表名

1.普通插入

insert into 表名 values (列元素, 列元素);

2.指定列插入

insert into 表名 (指定列名, 指定列名) values (列元素, 列元素);

3.指定多列插入

insert into 表名 (指定列名, 指定列名) values (列元素, 列元素), (列元素, 列元素);

1.全列查询

select * from 表名;

全列查询是危险操作,会涉及到大量的硬盘和网络访问。

2.指定列查询

select 列名, 列名 from 表名;

指定列查询的结果是临时表。

3.查询时带表达式

select name, chinese + 10 from exam;

select name, chinese + math + english from exam;

4.查询时带别名

select name, chinese + math + english as "总成绩" from exam;

5.针对查询结果去重

select distinct 列名 from 表名;

6.条件查询

条件成立,这一行作为查询结果。

运算符含义
>、>=、<、<=大于、大于等于、小于、小于等于
=NULL=NULL结果是NULL
<=>NULL=NULL结果是TURE(1)
!=不等于
value BETWEEN a0 AND a1范围是[a0, a1],NOT BETWEEN则取反
value IN如果value在option列表中,则返回TRUE(1),NOT IN则取反
列名 IS NULL是NULL
列名 IS NOT NULL不是NULL
LIKE模糊匹配,% 表示任意多个字符(包含0个); _ 表示任意一个字符。NOT LIKE相反
AND
OR
NOT

NULL和任何比较都为FALSE!

6.1 基本查询

select name, english from exam where english > 60;

select name, chinese, english from exam where chinese > english;

select name FROM exam where (chinese + math + english) > 200;

带别名无法带入条件

6.2 条件查询

select name, english, chinese from exam WHERE english > 60 and chinese > 60;

select name, english, chinese from exam WHERE english > 60 or chinese > 60;

AND的优先级比OR高。

6.3 范围查询

select name, english from exam where english between 60 and 80;

select name, english from exam where english IN (77, 88, 99);

BETWEEN ... AND ...范围为闭区间

6.4 模糊查询(% _)

select name from exam where name like "孙%";
# 查询所有以孙开头的名字

select name from exam where name like "孙_";
# 查询所有以孙开头的名字,孙后面只有一个字

select name from exam where name like "孙__";
# 查询所有以孙开头的名字,孙后面只有两个字

模糊查询效率较低。

6.5 NULL查询

select * from exam where chinese <=> NULL;
# 查询语文成绩为空值

7.排序查询

select 列名 from 表名 order by 列名 [desc], 列名 [desc];

select * from exam order by chinese desc, math;

select * from exam order by chinese + math + english

select name, chinese + math + english as total from exam order by total desc;

select name, chinese from exam where chinese is not null ORDER BY chinese desc;
  1. 默认升序排列
  2. NULL默认是最小的
  3. 使用desc降序排列
  4. 默认asc升序排列
  5. 输出结果是临时表
  6. 列名在前优先级更高
  7. 排序查询可用as带别名查询

8.分页操作

通过limit N offset M;或者limit M, N;子句获取从第M条数据开始(从0开始记录),往后N条数据。

不存在越界,最多返回空数据

update 表名 set 列名 = 值 where... order by... limit N;

不写where条件默认更改所有行。

delete from 表名 where... order by... 列名 limit N;

如果没有条件,会删除所有数据

截断表

插入查询结果

把查询的结果插入到另一个表内。

insert into 表名 [(列名)] select 列名 from 表名;

  1. 查询出来的列数和表结构(类型)相匹配
  2. 列的名字可以不一样

聚合查询

聚合查询,就是在进行行和行之间的运算。需要借助聚合函数运算,一般是Sql库函数。

函数说明
COUNT(列名)返回查询到的数据的数量
SUM(列名)返回查询到的数据的总和
AVG(列名)返回查询到的数据的平均值
MAX(列名)返回查询到的数据的最大值
MIN(列名)返回查询到的数据的最小值

1.COUNT

统计exam表中有多少个记录

select count(*) from 表名;

  1. 指定查询某个列的行数时,会忽略含NULL
  2. count()内只支持一个参数,其他一样

2.SUM

select SUM(*) from 表名;

3.AVG

select AVG(*) from 表名;

平均值可能和原列类型不一致

4.MIN

select AVG(*) from 表名;

分组查询

把表的若干行分组,需要进行“分组依据”。
指定某一个列,这个列值相同的行就会分到同一个组里。

select 表达式 from 表名 group by 列名;

select role, AVG(salary) from emp GROUP BY role;
# 统计相同职位的薪资平均数,并返回职位和薪资临时表

SELECT role, AVG(salary) from emp where name != '博哥' GROUP BY role;
# 统计除"博哥"之外相同职位的薪资平均数,并返回职位和薪资临时表(查询前分组)

SELECT role, AVG(salary) from emp GROUP BY role having AVG(salary) < 15000;
# 统计相同职位的薪资平均数,并返回职位和薪资<15000的临时表(查询后分组)

查询前分组用WHERE,查询后分组用HAVING,可以同时使用

内置函数

函数一般都是通过select语句调用的,把函数放到列的位置

1.时间函数

函数说明
CURDATE()返回当前日期
CURTIME()返回当前时间
NOW()返回当前日期和时间
DATE(date)提取datetime表达式的日期部分
ADDDATE(date, INTERVAL 单位)向日期值添加时间
SUBDATE(date, INTERVAL 单位)向日期值减去时间
DATEDIFF(expr1, expr2)两个日期的差,expr1 - expr2

2.字符串函数

函数说明
CHAR_LENGTH(str)返回字符长度
LENGTH(str)返回字节长度
CONCAT(str1, str2, ...)返回拼接后字符串
CONCAT_WS(分隔符, str1, str2, ...)返回拼接后带分隔符字符串
LCASE(str)字符串转换成小写,LOWER()
UCASE(str)字符串转换成大写,UPPER()
HEX(str), HEX(N)将字符串、数字转换成16进制
INSTR(str, substr)返回substring第一次出现的索引
INSERT(str, pos, len, newstr)在指定位置插入字符串,最多不超过指定的字符数
SUBSTR(str FROM pos FOR len)返回指定的子字符串
REPLACE(str, from_str, to_str)把字符串str中所有的from_str替换为to_str,区分大小写
STRCMP(expr1, expr2)逐个比较两个字符串,返回-1, 0, 1
LEFT(str, len), RIFHT(str, len)返回字符串str中最左/右边的len个字符
LTRIM(str), RTRIM(str), TRIM(str)删除给定字符串的前导、末尾、前导和末尾的空白符
TRIM [{LEADING / TRAILING / BOTH}] [remstr] FROM删除给定字符串的前导、末尾、前导和末尾的指定字符

3.数学函数

函数说明
ABS(X)返回X的绝对值
CEIL(X)返回不小于X的最小整数值
FLOOR(X)返回不大于X的最大整数X
CONV(N, from_base, to_base)不同进制之间的转换
FORMAT(X, D)将数字X化为“#, ###, ###”的格式,四舍五入到小数点后D位,以字符串形式返回
RAND([N])返回一个随机浮点值
ROUND(X), ROUND(X, D)将参数X舍入到小数点后D位
CRC32(expr)计算指定字符串的循环冗余校验值并返回一个32位无符号整数

4.其他函数

函数说明
version()显示当前数据库版本
database()显示当前正在使用的数据库
user()显示当前用户
md5(str)对一个字符串进行md5摘要,摘要后得到一个32位字符串
ifnull(val1, val2)如果val1为NULL,返回val2,否则返回val1

五、数据库约束

数据库约束会针对表的数据内容做出限制。在创建表的时候,跟随表的语句,一起被设置到数据库服务器中。

MySQL约束类型说明
NOT NULL 非空约束指定非空约束的列不能存储NULL值
DEFALUT 默认约束当没有给列赋值时候使用默认值
UNIQUE 唯一约束指定唯一约束的列每行数据必须有唯一值
PRIMARY KEY 主键约束NOT NULL 和 UNIQUE 的结合,可以指定一个列或多个列,有助于防止数据重复和提高数据的查询性能
FOREIGN KEY 外键约束用于定义两个表之间的关联关系,可以确保数据的完整性和一致性
CHECK 约束用于限制列或数据在数据库中的值,确保数据的准确性和可靠性
# 非空约束
create table student(id int NOT NULL, student varchar(20));
insert into student values(null, null); # 报错

# 默认约束
create table student(id int, student varchar(20) DEFAULT "无名氏");
insert into student values(1); # 默认名字为“无名氏”

# 唯一约束
create table student(id int, student varchar(20) UNIQUE);
insert into student values(1, "张三"); # NULL和NULL不算重复

# 联合主键
create table student(name varchar(20), class varchar(20), primary key(name, class));

# 自增主键
create table student(id int primary key auto_increment, class varchar(20)); 
insert into student values(1, "张三");
insert into student values(2, "张三");
insert into student values(null, "张三"); # 自增为3

# 外键约束
create table class(class_id int primary key auto_increment, class_name varchar(10));
create table student(student_id int primary key auto_increment, student_name varchar(10),
    class_id int, foreign key(class_id) references class(class_id));
# 学生班级id必须是班级表存在的班级id
    
# 自定义约束
create table student(id int, name varchar(20), age int, gender varchar(1),
    check(age > 18), check(gender = "男" or gender = "女"));
# 学生年龄必须大于18,性别必须是男或女
  1. 一个表只能有一个主键,但是多个列可以作为主键
  2. 针对整数,可以自增主键PRIMARY KEY
  3. 自增主键只会在最大数自增,且即使数据删除,最大数也会保留
  4. 针对外键约束,指的是两个表之间的关联。假设一共只有三个班级,分别是class1, class2, class3。如果出现了有学生在class4这个班级的不合法数据,就应该由外键约束控制。换句话说,是班级表约束学生表,班级表是父表,学生表是子表。其次,父表约束子表的同时,子表也在约束父表。父表的删除/修改操作,也会收到子表外键的约束
  5. 针对主键列,自带索引。如果一个列没有主键,那么这个列不带有索引,就不能被外键约束。因为关联表必须带有索引!

六、数据库设计

三大范式

1.第一范式

数据表的每一列都是不可分割的原子数据项,而不是集合,数据,对象等非原子数据。

2.第二范式

在满足第一范式的基础上,不存在非关键字段对任意候选键部分函数依赖,存在于表中定义了复合主键的情况。

复合主键:即联合主键,一个主键包含了多个列。
候选键:当一个主键只包含一个列时,一个列就是候选键;当一个主键包含多个列时,其中每一个被包含的列叫候选键。
非关键字段:除候选键之外的列。
完全函数依赖:通过整个主键,确定一个数据。
部分函数依赖:通过主键中的一部分,确定一个数据。

即:一张表中的非主键列,必须由整个主键确定

3.第三范式

再满足第二范式的基础上,不存在非关键字段,对任一候选键的传递依赖。

ID学号姓名年龄性别学院学院电话学院地址
1001张三18经济管理学院8888-8888上海市浦东新区
2002李四18信息工程学院6666-6666上海市浦东新区

此表的学院电话和学院地址附属于学院,学院又附属于主键ID,则发生了传递依赖。通过分表来消灭传递依赖。

ID学号姓名年龄性别
1001张三18
2002李四18
ID学院学院电话学院地址
1经济管理学院8888-8888上海市浦东新区
2信息工程学院6666-6666上海市浦东新区

设计过程

1.从业务逻辑中抽象的到抽象类/实体类

类对应的数据库中的表,类的属性对应数据表中的列。

2.确定实体和实体之间的关系,并画出E-R图

即表和表之间的关系:一对一,一对多,多对多。

3.根据E-R图完成Sql语句并创建数据库

  1. 表关系为一对一或一对多时,附表应该包含主表主键。
  2. 表关系为多对多时,引入一张关联表。

七、联合查询

联合查询/多表查询的前提,是多表之间存在一定关系。

笛卡尔积

笛卡尔积的结果是一张更大的表。列数是两列之和,笛卡尔积的行数是两行之积。

select * from student, class;
+------+-------+----------+----------+------------+
| id   | name  | class_id | class_id | class_name |
+------+-------+----------+----------+------------+
|  100 | meat1 |        1 |        2 | java101    | 
|  100 | meat1 |        1 |        1 | java100    |
|  101 | meat2 |        1 |        2 | java101    |
|  101 | meat2 |        1 |        1 | java100    |
|  102 | meat3 |        2 |        2 | java101    |
|  102 | meat3 |        2 |        1 | java100    |
+------+-------+----------+----------+------------+

通过排列组合后可以把两个表的所有情况都罗列出来,但其中这里包含了无效数据。此时发现其中对应列的值相同,此时这个数据就应该被筛选出来作为合理的数据-连接条件。

select * from student, class where student.class_id = class.class_id;
# 通过表名.列名取消歧义
+------+-------+----------+----------+------------+
| id   | name  | class_id | class_id | class_name |
+------+-------+----------+----------+------------+
|  100 | meat1 |        1 |        1 | java100    |
|  101 | meat2 |        1 |        1 | java100    |
|  102 | meat3 |        2 |        2 | java101    |
+------+-------+----------+----------+------------+

进一步,基于这个结果,添加其他条件,满足其他的查询条件。

  1. 进行笛卡尔积
  2. 通过连接条件,进行筛选
  3. 根据实际需求场景,进一步添加其他条件
  4. 针对列进行筛选/计算/分组/聚合

内连接

上述代码编写的联合查询,也是在进行内连接,只是语法不同。

select * from 表1 inner [inner] join 表2;

select * from 表1 inner [inner] join 表2 on 条件;

select * from 表1 inner [inner] join 表2 on 条件 where 其他条件;

select 表名.列名 from 表1 [inner] join 表2 on 条件 where 其他条件;

外连接

对于MySQL来说,外连接分为左外连接'left join'、右外连接'right join',但不支持全外连接。

现在有两张表,他们的数据是一一对应的。

student
+------------+------+
| student_id | name |
+------------+------+
|          1 | 张三 |
|          2 | 李四 |
|          3 | 王五 |
+------------+------+

score
+------------+-------+
| student_id | score |
+------------+-------+
|          1 |    90 |
|          2 |    80 |
|          3 |    70 |
+------------+-------+

此时外连接和内连接是完全等价的。

select * from student join score on student.student_id = score.student_id;
# 内连接
+------------+------+------------+-------+
| student_id | name | student_id | score |
+------------+------+------------+-------+
|          1 | 张三 |          1 |    90 |
|          2 | 李四 |          2 |    80 |
|          3 | 王五 |          3 |    70 |
+------------+------+------------+-------+

select * from student left(right) join score on student.student_id = score.student_id;
# 外连接
+------------+------+------------+-------+
| student_id | name | student_id | score |
+------------+------+------------+-------+
|          1 | 张三 |          1 |    90 |
|          2 | 李四 |          2 |    80 |
|          3 | 王五 |          3 |    70 |
+------------+------+------------+-------+

如果两张表的数据不是一一对应时,左右外连接结果不同。

select * from student left join score on student.student_id = score.student_id;
# 左外连接
+------------+------+------------+-------+
| student_id | name | student_id | score |
+------------+------+------------+-------+
|          1 | 张三 |          1 |    90 |
|          2 | 李四 |          2 |    80 |
|          3 | 王五 |       NULL |  NULL |
+------------+------+------------+-------+

select * from student right join score on student.student_id = score.student_id;
# 右外连接
+------------+------+------------+-------+
| student_id | name | student_id | score |
+------------+------+------------+-------+
|          1 | 张三 |          1 |    90 |
|          2 | 李四 |          2 |    80 |
|       NULL | NULL |          4 |    70 |
+------------+------+------------+-------+

外连接一般用于查询多余数据。

自连接

通过自连接,把行和行之间的关系,转成列和列之间的关系。查询时需要指定别名!

select * from score s1, score s2;

假设有两张表,一张学生表,一张成绩表。

student
+------+------+
| id   | name |
+------+------+
|    1 | 张三 |
|    2 | 李四 |
|    3 | 王五 |
+------+------+

score
+------+------------+---------+
| id   | java_score | c_score |
+------+------------+---------+
|    1 |         86 |      90 |
|    2 |         75 |      84 |
|    3 |         90 |      86 |
+------+------------+---------+

查询Java成绩比C成绩高的学生。先不筛选列来观察情况:

select * from student 
   join score s1 on student.id = s1.id 
   join score s2 on student.id = s2.id 
   where s1.java_score > s2.c_score;
+------+------+------+------------+---------+------+------------+---------+
| id   | name | id   | java_score | c_score | id   | java_score | c_score |
+------+------+------+------------+---------+------+------------+---------+
|    3 | 王五 |    3 |         90 |      86 |    3 |         90 |      86 |
+------+------+------+------------+---------+------+------------+---------+

优化查询后就是想得到的结果:

select student.id, student.name, s1.java_score, s2.c_score from student 
   join score s1 on student.id = s1.id 
   join score s2 on student.id = s2.id 
   where s1.java_score > s2.c_score;
+------+------+------------+---------+
| id   | name | java_score | c_score |
+------+------+------------+---------+
|    3 | 王五 |         90 |      86 |
+------+------+------------+---------+

子查询

将查询出来的结果,作为下一个查询条件。

假设有一张学生表:

+------+------+----------+
| id   | name | class_id |
+------+------+----------+
|    1 | 张三 |        1 |
|    2 | 李四 |        1 |
|    3 | 王五 |        2 |
+------+------+----------+

查询和“张三”同一个班级的学生,可以用两个SQL语句。

select class_id from student where name = "张三";
+----------+
| class_id |
+----------+
|        1 |
+----------+

select name from student where class_id = 1 and name != "张三";
+------+
| name |
+------+
| 李四 |
+------+

也可使用子查询将两句合并为一句。虽然SQL语句更少了,但可读性变差了。

select name from student where class_id = (
    select class_id from student where name = "张三"
    ) and name != "张三";
+------+
| name |
+------+
| 李四 |
+------+

当子查询返回多个列时,用IN关键字。

select * from 表名 where 列名 in (
    select ...
    );

合并查询

把多个查询结果,合并成一个查询结果。使用UNIONUNION ALL关键字。

select * from student where id = 1
    union all
    select * from student;
+------+------+----------+
| id   | name | class_id |
+------+------+----------+
|    1 | 张三 |        1 |
|    2 | 李四 |        1 |
|    3 | 王五 |        2 |
|    1 | 张三 |        1 |
+------+------+----------+

使用UNION会剔除重复数据,UNION ALL则不会。但需确保两组合并的结果,列类型和个数需匹配。

八、索引

索引在数据库中是一个特殊的数据结构,可以加快查询速度,但会占据额外的存储空间。后续修改数据的时候,也会有额外的开销。

索引的数据结构

在日常开发中,数据库的查询操作占比最高。因此要选择一种查询效率最高的数据结构。二叉搜索树会由于不平衡的问题导致一边子树的长度过长。AVL树虽严格控制左右子树的长度,但会给增删改的操作制造更多的开销。而哈希表只能处理“相等”查询,不能处理“范围”查询。

因此,就出现了一种为数据库量身定制的数据结构:B+树。想了解B+树,就要知道B+树的前身:B树。

B树是一个N叉搜索树。每个结点保存N个值,每个结点有N + 1个子树。因此和红黑树相比,相同数目的元素B树比红黑树低很多。虽然此时在查询的时候要比较多次,但一次硬盘读取可以读多个值。

IUwcDnm3_1.png

B+树是基于B树进行改进的,也是一个N叉搜索树。

  1. 每个结点保存N个值,每个结点有N个子树
  2. 父结点的值会在子结点中以最大值的身份出现。最后叶子结点的内容是整个数据集合的“全集”
  3. 由于当前的叶子结点是全集,因此只使用叶子结点存储涉及到的“数据行”,非叶子结点,只存储“索引列”

IUwcDnm3_2.png

B+树相比于其他数据结构作为索引的优势:

  1. 作为一个N叉搜索树,高度更低。在查询的时候,访问硬盘的次数更少
  2. 所有的数据行都在叶子结点上,所有的查询操作最终都要落到叶子结点上。访问硬盘次数更稳定
  3. 叶子结点作为链表,适合进行范围查询
  4. 非叶子结点只存储ID,占据的空间小

B+树上的结点,在MySQL内部称为“页”。默认大小是16KB。

  1. 索引页(非叶子结点)只存储索引列
  2. 数据也(叶子结点)存储数据行
  3. 对于非主键列创建的索引,虽然也是B+树,但叶子节点就不是数据行了,而是主键id。
  4. 一个表可以有多个索引(B+树),只需要有一个B+树保存数据就行

索引分类

一个表,可以有多个索引。

1.主键索引

创建表时根据主键创建的索引。

2.普通索引

通过SQL语句创建的索引。

3.唯一索引

创建的表带有UNIQUE关键字的。

4.聚集索引

对于InnoDB来说,就是主键索引。

5.非聚集索引

对于InnoDB来说,就是非主键索引。

6.索引覆盖

查询的内容已经包含在索引列中。

使用索引

1.建立索引

当一张表存在主键约束、外键约束或唯一约束时,MySQL会为其自动创建索引。如果不指定任何约束时,MySQL会自动为每一个表生成一个隐藏列并为其创建索引。

创建索引操作可能很危险,如果针对一张大表创建索引,会触发大量的硬盘IO。最好在创建表时指定索引。

1.1 主键索引

create table 表名 (列名 类型 primary key);

create table 表名 (列名 类型, primary key (列名));

create table 表名 (列名 类型 primary key);
alter table 表名 add primary key (列名);
alter table 表名 modify 列名 类型 auto_increment;

1.2 唯一索引

create table 表名 (列名 类型 unique);

create table 表名 (列名 类型, unique (列名));

create table 表名 (列名 类型);
alter table 表名 add unique (列名);

1.3 普通索引

create table 表名 (列名 类型, index (列名));

create table 表名 (列名 类型);
alter table 表名 add unique (列名);
       
create table 表名 (列名 类型);
create index 索引名 on 表名(列名);

2.建立复合索引

create table 表名 (列名1 类型, 列名2 类型, index (列名1, 列名2));

create table 表名 (列名1 类型, 列名2 类型);
alter table 表名 add unique (列名1, 列名2);
       
create table 表名 (列名1 类型, 列名2 类型);
create index 索引名 on 表名(列名1, 列名2);

3.查看索引

show keys from 表名;

show index from 表名;

desc 表名;

4.删除索引

4.1 主键索引

alter table 表名 drop primary key;

4.2 其他索引

alter table 表名 drop index 索引名;

5.索引失效问题

可以在SQL语句前使用EXPLAIN关键字查看是否包含索引。当TYPE列显示All为全表查询,const为索引。

  1. 查询操作时没有使用索引列
  2. 查询索引列时索引值较单一,例如"男"和"女"
  3. 查询时针对索引列进行表达式运算
  4. 查询条件中包含了OR,一边是索引列的条件,一边是非索引列的条件
  5. 字符串查询时包含了LIKE时,通配符%_放在字符串开头
  6. 查询索引列时条件包含!=NOT
  7. 多个表联合查询时,但表的字符集不一致
  8. 设定复合索引时(例如a,b,c),使用b或c或b,c查询

九、事务

开发中,某个功能需要多个SQL配合完成。事务可以把多个SQL打包成一个整体,要么全部执行完,要么全都不执行(回滚到执行前状态),避免出现执行一般的情况。

事务的四个核心特性:

  1. 原子性:多个SQL是一个整体
  2. 一致性:数据正确符合实际情况
  3. 持久性:事务对数据做出的修改能够持久生效(写入硬盘)
  4. 隔离性:讨论并发情况下可能出现的问题:脏读问题、不可重复读问题和幻读问题
    • 脏读:第一个事务正在对数据进行修改时,被第二个事务读取,此时读取的数据可能是脏数据。此时应规定未被提交的修改,不能被其他事务读取到。
    • 不可重复读:同一个事务多次读取同一个数据结果不同。此时应给“读取”操作加锁。
    • 幻读:同一个事务针对某个查询,多次读取到的“结果集”(查询得到的临时表)不同。此时需要让服务器对事务进行彻底“串行化”。

事务操作

START TRANSACTION; # 开启事务

COMMIT; # 提交事务

ROLLBACK; # 回滚事务

SAVEPOINT 保存点名; # 设置保存点

ROLLBACK TO 保存点名; # 回滚到保存点

隔离级别

MySQL为程序员提供了多个“隔离级别”让程序员自主选择隔离性。可以通过SQL语句或MySQL的配置文件修改隔离级别。

  1. READ UNCOMMITTED -> 隔离性性最弱,并发性最强,效率最高 -> 脏读、不可重复读、幻读
  2. READ COMMITTED -> 隔离性提高,并发性降低,效率降低 -> 不可重复读、幻读
  3. REPEATABLE READ(默认)-> 隔离性提高,并发性降低,效率降低 -> 一部分幻读
  4. SERIALIZABLE -> 隔离性最高,并发性最低,效率最低

1.通过SQL语句(临时)

如果MySQL服务器重启,当前命令失效。

SELECT @@GLOBAL.transaction_isolation; # 查询隔离级别

SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL 隔离级别|访问模式; 

2.通过MySQL的配置文件(长期)

在 C:\ProgramData\MySQL\MySQL Server 8.0\my.ini" 文件中修改。

十、视图

视图是一个虚拟的表。它不占据存储空间,而是把其他的查询结果集临时保存下来,组织成表一样的结构。

创建视图

通过创建视图,把一系列查询语句返回的表临时保存起来。以后使用视图进行查询,相当于“子查询”。

CREATE VIEW 视图名 (列名1, 列名2...) AS 查询语句;

修改视图

一般来说,修改表时,视图会同时修改;修改视图,表也会同时修改。但不是所有视图都能修改,本质是因为修改视图不能与原始表一一对应。

  1. 当创建视图的时候,使用了聚合函数
  2. 视图关联的查询中,使用了子查询
  3. 视图关联的查询中,包含去重查询
  4. 视图关联的查询中,包含GROUP BYHAVING
  5. 视图关联的查询中,包含UNIONUNION ALL
  6. 视图关联的查询中,使用了不可修改的视图

删除视图

DROP VIEW 视图名;

十一、JDBC编程

实际开发中,很少使用JDBC进行数据库操作。

增删改操作

import com.mysql.cj.jdbc.MysqlDataSource; // 导入的MySql驱动包

import javax.sql.DataSource; // JDBC规范的核心接口
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Scanner;

public class Demo1 {
    public static void main(String[] args) throws SQLException {
    
        // 1. 创建“数据源”
        DataSource dataSource = new MysqlDataSource(); // JDBC定义接口,MySql驱动包实现接口
        ((MysqlDataSource) dataSource).setURL("jdbc:mysql://127.0.0.1:3306/meat" +
                "?characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true");
        ((MysqlDataSource) dataSource).setUser("root");
        ((MysqlDataSource) dataSource).setPassword("1234");

        // 2. 和数据库进行网络连接
        Connection connection = dataSource.getConnection(); // 封装了拨号和接通的过程,失败会抛出异常

        // 3. 构造SQL语句
        //    字符串的SQL不符合JDBC要求,需要SQL字符串构造出“语句”对象
        //    JDBC提供了 Statement 和 PreparedStatement
        //    使用 PreparedStatement 就能让SQL语句的解析操作放在客户端上完成
        Scanner sc = new Scanner(System.in);
        System.out.print("学号:");
        int id = sc.nextInt();
        System.out.print("姓名:");
        String name = sc.next();
        System.out.print("班级:");
        int class_id = sc.nextInt();
        String sql = "insert into student values(?, ?, ?)"; // ? 占位符
        PreparedStatement statement = connection.prepareStatement(sql);
        statement.setInt(1, id); // JDBC 在替换中进行检查校验,验证是否存在安全隐患
        statement.setString(2, name);
        statement.setInt(3, class_id);

        // 4. 发送SQL语句到服务器上执行
        int n = statement.executeUpdate(); // n 表示这样的操作影响了多少行

        System.out.println("n = " + n);

        // 5. 释放上述资源,且释放顺序和创建顺序相反
        statement.close();
        connection.close();
    }
}

查询操作

import com.mysql.cj.jdbc.MysqlDataSource;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class Demo2 {
    public static void main(String[] args) throws SQLException {

        // 1. 创建数据源
        DataSource dataSource = new MysqlDataSource();
        ((MysqlDataSource) dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/meat?" +
                "characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true");
        ((MysqlDataSource) dataSource).setUser("root");
        ((MysqlDataSource) dataSource).setPassword("1234");

        // 2. 连接数据库
        Connection connection = dataSource.getConnection();

        // 3. 构造SQL语句
        String sql = "select * from student";
        PreparedStatement statement = connection.prepareStatement(sql);

        // 4. 返回结果集
        ResultSet resultSet = statement.executeQuery();

        // 5. 遍历结果集
        while (resultSet.next()) {
            int id = resultSet.getInt("id");
            String  name = resultSet.getString("name");
            int class_id = resultSet.getInt("class_id");
            System.out.println(id + " " + name + " " + class_id);
        }

        // 6. 释放资源
        resultSet.close();
        statement.close();
        connection.close();
    }
}