参考
1 查询sql语句的总体结构
写sql语句的顺序:
select 输出
from 获取表中的数据
where 对 from 得到的数据进行过滤。and. or .in .like . != 等等
group by 分组
having 过滤
order by 排序
limit 限定个数
执行sql语句的顺序
- from
- where
- group by
- select
- having
- order by
- limit
2 group by
2.1 例子1:
id | name | classid |
---|---|---|
1 | A | 1 |
2 | B | 1 |
3 | C | 2 |
4 | D | 2 |
5 | E | 1 |
6 | F | 1 |
7 | G | 2 |
select id,name,classid from stu where id%2 = 0 group by classid
先执行where过滤。之后把数值相同的分为一组,此时根据classid分为了两组。把每组的第一行当作各自组的标识,select只输出标识。
输出:
原表的第二行和第四行
id | name | classid |
---|---|---|
2 | B | 1 |
4 | D | 2 |
2.2 例子2 加聚集函数count
数据表格同例子1。
selec count(1), classid from stu group by classid
相当于在原表中添加了一列temp(恒等于1),一列count,每个元组都会在自己的count属性上设置为相应的计数值。
输出:
count(1) | classid |
---|---|
4 | 1 |
3 | 2 |
2.3 例子3 count+distinct
数据表格同例子1。
sql1
select count(name), classid from stu group by classid 不去重
和上例的count(1)的区别:如果name中有null,那么count不计入null。其他情况二者一样。
sql2
select count(distinct name), classid from stu group by classid 去重
2.4 例子4
查询每个班中人数大于2的班级号和人数
select count(*) as n, clid
from stu
group by clid
having n > 2
(order by n desc)
前四行等价于
select clid
from stu
group by clid
having count(*) > 2
注意此时先执行select(添加了一列count),再执行having
2.5 聚集函数
id | name | classid |
---|---|---|
1 | A | 1 |
2 | B | 1 |
3 | A | 1 |
4 | C | 2 |
5 | D | 2 |
from stu group by clid,name
clid和name都相同的分为一组
上图的就被分为了4组
与group by搭配的常用聚集函数
1. count
2. sum
3. max
4. min
5. avg
6. group-concat: 字符串的集合,不常用
sql1
select sum(1), classid from stu group by classid
可以看作等于count(1)
sql1.1
select sum(2), classid from stu group by classid
可以看作等于 count(2) * 2
sql2
selec count(classid), classid from stu group by classid
当前分组中不为null的classid 的总和
sql3
selec count(distinct classid), classid from stu group by classid
当前分组中不为null且不重复的clid的总和
3 case语句
case
when then 输出
when then 输出
else 输出
end
例子1:
将1班的学生改为2班,2班的改为1班
update stu
set classid = (case when classid=1 then 2 when classid=2 then 1 end)
如果只是想将输出结果进行改变,而不是修改原数据,则用select case when
格式:
select stuid,stuname,
(case when classid=1 then 2 when classid=2 then 1 end) as newClassid
from stu
例子2:聚集函数加case
查询:name 基础成绩 爬虫成绩 sql成绩(3列的表格重新组合为4列的形式)
day2 p03
select name,
max(case when 学科=‘基’ then score else null end) as 基,
max(case when 学科=‘爬’ then score else null end) as 爬,
max(case when 学科=‘sql’ then score else null end) as sql,
from socre
group by name
注意先执行group再执行select。case会把每一行都扫描一遍。其中max函数可以变成其他min、avg等。
4 join连接
4.1 普通join
假设stu表有m个元素,3种属性,
class表有n个元素,3种属性,
-
stu join class where XXX
:两张表的所有数据都要组合起来,会生成m*n条数据量,产生6种属性。
其中把stu join class
看作一张表。 -
stu join class on stu.classid = class.id where XXX
on是在where之前的操作,生成的数据量会小(与没有on,直接where相比)。
其中把stu join class on stu.classid = class.id
看作一张表。
单纯join,on和where都可以用。
4.2 left join
和join的区别:
- left join 必须有on条件
- left join在
join on
的前提下,会查看左边表的数据是不是都包含在新生成的表中,如果都包含了,则和join没区别;如果不是,用null与不包含的行组成新行加入新表(也就是必须让左表的数据都出现在新表之中)。
例子
假设stu有3条数据,class有1条数据。
stu表
id | name | classid |
---|---|---|
1 | A | 1 |
2 | B | 1 |
3 | C | 2 |
class表
id | name | manager |
---|---|---|
1 | O | 黄 |
class left join stu
on class.id = stu.classid
会生成2条数据。
class.id | name | manager | stu.id | name | classid |
---|---|---|---|---|---|
1 | O | 黄 | 1 | A | 1 |
1 | O | 黄 | 2 | B | 1 |
先join on生成了两条,然后再检查class中的所有元素是不是都出现了,此时已经都出现了。
4.3 right join
A right join B
等价于
B left join A
4.4 例子
4.4.1 例子1
上面是stu表,下表是class表
id | name | classid |
---|---|---|
1 | A | 1 |
2 | B | 1 |
3 | C | 2 |
4 | D | 2 |
id | name | manager |
---|---|---|
1 | X | 蓝 |
查询哪些同学没有班级。
用join只能查出来有班级的同学,此时需要用left join来补充null。
select stu.name
from stu left join class
on classid = class.id
where class.id is null
此时如果用一个子查询,sql语句如下,查询结果是什么样子:
select name,
(select id from class where manager='蓝')
from stu;
当id返回单个值的时候,会创建一个新列class.id
stu.id | class.id |
---|---|
1 | 1 |
2 | 1 |
3 | 1 |
4 | 1 |
假设class表变成如下,此时id返回多值的时候,会报错:
id | name | manager |
---|---|---|
1 | X | 蓝 |
2 | Y | 黄 |
3 | Z | 蓝 |
4.4.2 例子2
-
查询 01 课程比 02 课程成绩高的学生的信息及课程分数。
同一个表中的数据(‘01’课程的成绩和‘02’课程的成绩)进行对比,一般需要自己join自己。# 连续两次join select c_01.score as '01分数',c_02.score as '02分数', s.sname from SC c_01 join SC c_02 on c_01.SId = c_02.SId and c_01.CId = '01' and c_02.CId = '02' join Student s on s.SId = c_01.SId where c_01.score > c_02.score
-
查询同时选择了 01 课程和 02 课程的学生
// 第一步找到了符合条件的学生id select s1.SId from SC s1 join SC s2 on s1.SId=s2.SId and s1.cid='01' and s2.cid='02' // 如果使用子查询,完整如下 select * from Student where SId in ( select s1.SId from SC s1 join SC s2 on s1.SId=s2.SId and s1.cid='01' and s2.cid='02' ) // 如果使用join,完整如下 select st.* from SC s1 join SC s2 on s1.SId=s2.SId and s1.cid='01' and s2.cid='02' join Student st on s1.SId=st.SId
-
查询 没有学全所有课程的学生的信息
# 1 NOT IN方式。先找出学全了的学生。三重嵌套 select * from Student where SID not in ( select SId from SC group by SId having count(1) = (select count(1) from Course) ) # left join的方式 select st.* from student st left join sc on sc.SId = st.SId group by st.sid having count(1) < (select count(1) from Course)
4.5 子查询
select、 where、from中都可以有子查询。
from的子查询例子,必须有别名
select stu.name
from stu join (select * from class where manager='蓝') as C
on stu.classid = C.id
5 半连接
子查询内有引用父查询中的表的信息
stu表:
id | name | classid |
---|---|---|
1 | A | 1 |
2 | B | 1 |
3 | C | 2 |
4 | D | 2 |
5 | E | 3 |
查找每个班级中学号最大的学生的信息
子查询的方式:
select * from stu
where id in (
select max(id) # 子查询中,表新增了一列,value是对应分组的id最大值
from stu
group by classid
)
半连接的方式:
select *
from stu s1
where id = (select max(id) from stu s2 where classid=s1.classid)
半连接方式的过程:
- from stu s1:取得表中的5条数据
- 父查询的where:逐条执行。假设此时执行的是第一条数据,那么外层id为1,内部子查询就变成了
select max(id) from stu s2 where classid=1
,内部子查询返回的结果是2
,整个查询相当于select * from stu s1 where 1 = 2 # false
- 接下来执行第二条数据。那么外层id为2,内部子查询就变成了
select max(id) from stu s2 where classid=1
,内部子查询返回的结果是2
,整个查询相当于select * from stu s1 where 2 = 2 # true
6 事务的并发控制
6.1 多事务执行方式
- 事务串行执行
不能充分利用系统资源 - 交叉并发执行
并行事务的并行操作轮流交叉运行。是单处理机系统的并发方式,能提升系统的效率。 - 同时并发方式
多处理机系统,每个处理机运行一个事务,实现了多个事务真正的并行运行。并发方式机制较为复杂。
并发执行的问题:
存取和存储不正确的数据,破坏事务的隔离性和数据库的一致性。
6.2 数据不一致性
- 丢失修改
事务1和事务2从数据库中同时读取一个数据并修改,事务2的提交结果破坏了事务1提交的结果,导致事务1的修改被丢失。
- 不可重复读
事务2读取数据后,事务1执行更新,导致事务2无法再现前一次读取结果。
不可重复读
还有两种情况称之为幻影读
:
- T1 读取某个范围的数据,T2 在这个范围内插入新的数据,T1 再次读这个范围的数据,此时读取的结果和和第一次读取的结果不同。
- T1 读取某个范围的数据,T2 在这个范围内删除部分数据,T1 再次读这个范围的数据,此时读取的结果和和第一次读取的结果不同。
- 读脏数据
T1 修改一个数据,T2 随后读取这个数据。如果 T1 撤销了这次修改,那么 T2 读取的数据是脏数据。
6.3 封锁
封锁就是事务T在对某个数据对象操作之前,对其加锁。加锁后事务T就对该数据对象有了一定的控制,在T释放它的锁之前,其他的事务不能更新此数据对象。
基本封锁类型
- 排他锁,写锁,简记为X锁
如果事务T对数据对象A加了X锁,则只允许T读取和修改A,其他任何事物都不能在对A加任何类型的锁,直到T释放A上的锁。 - 共享锁,读锁,简记为S锁、
若事务T对数据对象A加了S锁,则其他事务只能对A加S锁,而不能加X锁,直到T释放A上的锁。
封锁技术带来的问题:活锁
数据资源释放时间不确定,导致某些事务长时间等待,得不到封锁的机会。先来的却可能等待很久。
采用先来先服务策略。
封锁技术带来的问题:死锁
多个事务各自占有部分资源等待另一部分资源,资源需求出现回路,导致事务停顿得不到执行。
预防死锁: 就是要破坏死锁产生的条件,包含一次封锁法和顺序封锁法。
诊断与解除死锁: 定期检测是否存在死锁,诊断死锁包含超时法和等待图法。解除死锁的方式是选择一个处理死锁代价最小的事务并将其撤销。
6.4 并发调度的可串行性
以不同的顺序串行执行多个事务有可能会产生不一致的结果,但不会让数据库处于不一致状态,所以可以认为是正确的。
几个事务的并行执行是正确的<=>其并行执行的结果与某一种串行执行时的结果相同。
这种并行调度策略称为可串行化的调度策略。
6.5 两段锁协议Two-Phase Locking
事务遵循两段锁协议是保证可串行化调度的充分条件。
在运用X锁和S锁这两种基本封锁时,还需要约定一些基本规则,例如何时申请X或S锁,何时释放等,称这些规则为封锁协议。
三级锁协议:在不同程度上保证数据一致性
一级封锁协议
:事务T在修改数据R之前必须先对其加X锁,直到事务结束才释放。防止丢失修改。
二级封锁协议
:一级封锁协议的基础上添加:事务T在读取数据R之前必须先加S锁,读完后释放S锁。除了防止丢失修改之外,还防止了读脏数据。
三级封锁协议
:一级封锁协议的基础上添加:事务T在读取数据R之前必须先加S锁,直到事务结束才释放S锁。除了防止丢失修改之外,还防止了读脏数据和不可重复读。
两段锁协议:保证并发调度的正确性
“加锁”阶段只能加锁,“解锁”阶段只能解锁。加锁和解锁分为两个阶段进行。
- 并行执行的所有事务均遵守两段锁协议,则对这些事务的所有并行调度策略都是可串行化的。
- 所有遵循两段锁协议的事务,其并行执行的结果一定是正确的。
- 事务遵循两段锁协议时可传性化调度的充分条件,不是必要条件。
- 遵循第三季封锁协议必然遵守两段锁协议。
两段锁协议可能发生死锁:
T1: Slock B;读取B
T2: Slock A;读取A
T1: Xlock A; 等待
T2: Xlock B; 等待
两段锁协议和一次封锁法:
- 一次封锁法要求每个事务必须一次性将所有要使用的数据全部加锁,因此一次封锁法遵循两段锁协议。
- 但是两段锁协议并不要求事务必须一次将所有要使用的数据全部加锁,因此两段锁协议可能发生死锁。
7 索引
7.1 索引分类
- 单值索引:即一个索引值包含单个列,一个表可以有多个单列索引。
- 唯一索引:索引列的值必须唯一,但允许有空值。
- 复合索引:即一个索引值包含多个列
7.2 索引结构
不同存储引擎具有不同的索引类型和实现。
7.2.1 B Tree
BTree是多路平衡查找树。有的数据只在非叶子节点上。所有的叶子节点都位于同一层。
7.2.2 B+ Tree
- 是大多数 MySQL 存储引擎的默认索引类型。
- 基于B Tree和叶子节点顺序访问指针。具有B Tree的平衡性,并且通过顺序访问指针来提高区间查询的性能。所有数据都要出现在叶子节点。
- 非叶子结点不存放真实的数据,只存储指引索引方向的数据项。
B树与B+树对比
B树与B+树都是对磁盘友好的数据结构,能大幅降低磁盘访问次数。B树的优点在于数据存储在每个结点中,可以更快访问到,而不必须走到叶子结点,B树更多的用在文件系统中。B+树的每个非叶子结点都只充当索引,所以查询必须到叶子结点结束,但它十分适合“扫库”和区间查找,而且因为大多结点只用于索引,所以并不会存储真正的数据,在内存上会更紧凑,相同的内存就可以存放更多的索引数据了。比如字典的拼音和汉字是分离的,只需要几十页就能得到完整的拼音表,但是如果拼音和汉字掺杂在一起,要得到完整的索引(拼音)表就需要整个字典。B+树的这些特性使得它更适合用来做数据库的索引。
7.2.3 哈希索引
哈希索引能以 O(1) 时间进行查找,但是失去了有序性:
- 无法用于排序与分组;
- 只支持精确查找,无法用于部分查找和范围查找。
InnoDB 存储引擎有一个特殊的功能叫“自适应哈希索引”,当某个索引值被使用的非常频繁时,会在 B+Tree 索引之上再创建一个哈希索引,这样就让 B+Tree 索引具有哈希索引的一些优点,比如快速的哈希查找。
7.2.4 全文索引
MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而不是直接比较是否相等。
查找条件使用 MATCH AGAINST,而不是普通的 WHERE。
全文索引使用倒排索引实现,它记录着关键词到其所在文档的映射。
InnoDB 存储引擎在 MySQL 5.6.4 版本中也开始支持全文索引
7.2.5 空间数据索引
MyISAM 存储引擎支持空间数据索引(R-Tree),可以用于地理数据存储。空间数据索引会从所有维度来索引数据,可以有效地使用任意维度来进行组合查询。
8 查询优化分析
使用 Explain
进行分析。
Explain 用来分析 SELECT 查询语句,开发人员可以通过分析 Explain 结果来优化查询语句。
9 基础JDBC
9.1 加载JDBC驱动并进行初始化
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
9.2 建立Connection连接
/**
* 取得数据库的连接
* @return 一个数据库的连接
*/
public static Connection getConnection(){
Connection conn = null;
try {
//初始化驱动类com.mysql.jdbc.Driver
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/database_name?characterEncoding=UTF-8","root", "password");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
9.3 创建Statement或者PreparedStatement接口,执行SQL语句
- 使用Statement接口
在Statement中使用字符串拼接的方式,该方式存在句法复杂,容易犯错等缺点.
Statement s = conn.createStatement();
// 注意: 字符串要用单引号'
String sql = "insert into t_courses values(null,"+"'数学')";
s.execute(sql);
- 使用PreparedStatement接口
String sql = "insert into t_course(course_name) values(?)";
PreparedStatement pstmt = (PreparedStatement) conn.prepareStatement(sql);
pstmt.setString(1, courseName); //给占位符赋值
pstmt.executeUpdate(); //执行
?
在这里就起到占位符的作用。这种方式除了避免了statement拼接字符串的繁琐之外,还能够提高性能。每次SQL语句都是一样的,数据库就不会再次编译,这样能够显著提高性能。
注意一点,这里的参数索引是从1
开始的
9.4 处理和显示结果
ResultSet rs = s.executeQuery(sql);
while (rs.next()){ // 注意是next(),不是hasNext
int courseId = rs.getInt("course_id");
String courseName = rs.getString("course_name");
// 还可以填该属性在数据表中的列号,从1开始编码
}
9.5 释放资源
- 关闭的过程中遵循从里到外的原则
/**
* 封装三个关闭方法
* @param pstmt
*/
public static void close(PreparedStatement pstmt){
if(pstmt != null){ //避免出现空指针异常
try{
pstmt.close();
}catch(SQLException e){
e.printStackTrace();
}
}
}
public static void close(Connection conn){
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
public static void close(ResultSet rs){
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
// TODO: handle exception
e.printStackTrace();
}
}
}