- 双向链表
- 二叉排序树
- 红黑树
- 散列表
-
什么是双向链表
双向链表是一种数据结构,是由若干个节点构成,每个节点由三部分构成, 分别是前驱节点,元素,后继节点,且双向链表中的节点在内存中是游离状态存在的。

-
插入元素 – add(int index,E ele)
其实就是断开链,重新形成链的过程
-
问题:LinkedList中index是下标吗?
双向链表中没有下标,在LinkedList中涉及到的所有的index都表示从头节点开始的第几个元素,注意index是从0开始的。
-
-
查找元素 – get(int index)
双向链表查找元素方式:对半查找
判断查找的index与链表长度一半的关系
若小于,则从头节点开始顺序查找
否则,从尾节点开始逆序查找
这样做,查询效率更高,同时注意,这里明确了index并不是下标,而是表示从头节点或尾节点开始顺序/逆序迭代多少个元素。在双向链表中,节点并没有index属性。如果查找的元素位于双向链表的头部或尾部,则查询效率很高;但是如果查询的元素位于链表的中间部分,查询效率是地下的。
-
删除元素 – remove(int index):E
同样是断开链,重新形成链的过程,注意,删除节点后,会将删除节点的元素对象返回
-
修改元素 – set(int index,E newEle):E
原理:修改节点中元素部分引用item的值
修改元素完成,会将节点中的原元素对象返回
-
LinkedList的数据结构在JDK1.8前后是不同的
- 从JDK1.8开始,其数据结构为双向链表
- 在JDK1.8之前,其数据结构为双向循环链表
关于双向循环链表:首尾相连的双向链表叫做双向循环链表。
-
双向循环链表查询操作:
和双向链表一样,遵循对半查找,即判断index与链表查询一半的大小关系:
小于size>>1,从header开始顺序查找
否则,从header开始逆序查找 -
位移运算:
带符号右移 表示除法运算 >>1 表示右移一位,除以2得效果
1 1 1 1 = 18+14+12+11=15
8 4 2 1 – 权重1111 对应得十进制是多少,计算方式是:各个位上得值*权重之和即为对应得十进制结果
是除以得效果原因: 右移后,原来得某个二进制对应得权重会降低2n倍,所以整个数也是一个除以2n得效果
eg: 101100
8421
<< 带符号左移,是乘以2^n得效果
-
常见面试题:
ArrayList和LinkedList有什么区别
数据结构不同:ArrayList底层采用数组实现;LinkedList底层采用双向链表实现
导致性能不同:
查询元素:
ArrayList查询性能很高,
LinkedList中若查询的元素位于头部或尾部部分,查询性能也挺高,如果查询的元素偏中间部分,则查询性能低下(查询元素越在中间位置,性能越低)
增删元素:
ArrayList若在尾部增删元素,性能可能会很高,在头部或中间增删元素(因为后续所有元素都需要移动)性能比较低下
LinkedList:若在头尾部进行增删元素,性能很高;但是如果在中间位置进行增删元素,性能同样不高结论:若在首尾进行增删元素,LinkedList性能高于ArrayList;否则在其他位置进行增删元素,ArrayList和LinkedList的性能是差不多的。查询元素Arraylist综合性能也是更好
结论:综合考虑,大部分情况选择ArrayList性能都是比较高的,若明确是在首尾增删,则选择LinkedList
-
什么是递归
递归是一种思想,应用在编程中体现为方法调用方法本身。
-
案例:要求用户输入一个正整数,写一段程序求出该整数的阶乘(求阶乘)
阶乘:某个数的阶乘=从这个数开始,依次乘以前一个数,直到乘以1为止
1!=1
2!=21 21!
3!=321 32!
4!=4321 43!
n! =n(n-1)*(n-2)…1 n(n-1)!解法:
//该方法用于求出给出整数n的阶乘
public int f(int n){
if(n==1)
return 1;
return n*f(n-1);
} -
递归的经典案例2:斐波那契数列
斐波那契数列是指这样一组数列
1 1 2 3 5 8 13 21 34 55 89.。。。。
规律为:第一位第二位固定为1,从第三位开始,其值为前两位之和,求出第13位的值是多少思路:定义一个方法f(int pos),用于求出给定位置pos上的值,返回值即为对应的值
public int f(int pos){
if(pos1 || pos2)
return 1;
return f(pos-1)+f(pos-2);
} -
递归注意事项:
- 递归一定要有出口,否则会造成栈内存溢出 – StackOverflowError(SOF)
- 递归是有深度的,使用递归时,必须考虑到深度问题,若深度太深,可能会造成栈内存溢出。
-
栈内存和递归使用的关系:
栈内存会存在若干个栈帧区域(栈帧:每调用一个方法,在栈中会为该方法开辟一块栈帧区域,用于保存这个方法内存产生的所有局部遍历,方法调用结束,对应的栈帧区域随之销毁),若递归没有出口,则在栈中会一直开辟栈帧区域,而栈内存大小是有限则,则最终一定会出现栈内存溢出(SOF)
-
内存溢出问题:
栈内存溢出:StackOverFlowError - SOF 在递归中可能会出现
堆内存溢出:OutOfMemoryError - OOM
造成OOM的原因:
1. 创建的对象太大了,堆内存中的剩余内存不足以分配给请求的资源
2. 内存泄漏的不断累积,最终也会造成堆内存溢出。内存泄漏:堆内存中分配出去的内存回收不回来,无法复用,这个现象叫做内存泄漏
内存溢出:剩余内存不足以分配给请求的资源,则会造成内存溢出。内存泄漏和内存溢出的关系:
内存泄漏累积到一定程序才会造成内存溢出,并不是内存泄漏一旦出现,则立即出现内存溢出
出现内存溢出并不一定是由于内存泄漏造成的,还可能是因为创建了大对象造成的。
-
定义:
树是指由若干个节点构成,其中有且仅有一个根节点。(每个节点中都会保存元素)
-
定义
度为2的树称为二叉树
-
定义
二叉排序树(Binary Sort Tree),又称二叉查找树(Binary Search Tree),亦称二叉搜索树。是数据结构中的一类。在一般情况下,查询效率比链表结构要高。
-
保存节点规则:
- 元素不能重复
- 左子树的值一定小于根节点
- 右子树的值一定大于根节点
-
数据结构可视化网站:https://www.cs.usfca.edu/~galles/visualization/Algorithms.html
-
查询效率为什么比链表结构要高
二叉排序树每次比较完成,都会排除将近一半的数据,所以查询效率会很高,而单向链表最次的情况,会查询n次。
-
如何保证二叉排序树中节点的元素是可比较大小的
二叉排序树中节点中保存的元素可以是任意引用类型。但是要求元素之间是必须可比较大小的,如何保证元素是可比较大小的?
让元素所属的类实现Comparable接口,定义比较规则。
即二叉排序树中的元素所属的类必须要求实现Comparable接口才可能。保存字符串类型的二叉排序树
-
比较器接口
-
Comparable接口
-
内比较器(实现该接口的类在类的内部重写compareTo方法来定义比较规则-比较规则定义在类的内部)
-案例:实现Student对象之间的比较
-练习1:一个集合中保存有若干个Student对象,现要求按照学生的id进行降序排列,请用代码实现。
对集合进行排序:Collections.sort(List)
-练习2:在练习1的基础上(不改变Student原有的排序规则),对list集合中的student重新排序,按照年龄进行升序排列。
-
-
Comparator接口 – 外比较器–比较规则是定义在类的外部的
适用场景:
当不想使用原来的排序规则,想使用新的或临时的排序规则时,就可以使用外比较器来实现。使用:实现Comparator接口,重写其中的抽象方法,在方法中定义新的比较规则接口。后续若对list进行排序,可使用api方法Collections.sort(List,Comparator)
练习3:以上案例中,要求在不改变Student默认排序规则的前提下,对集合中的学生重新排序,按照姓名降序排列。
注意点:若排序规则时根据某引用类型来比较,需要调用该类型的类中的compareTo方法来获取比较结果 -
手写二叉排序树
- 定义二叉排序树类,通过泛型由用户来指定元素类型,
注意: 泛型中的类型T一定是引用类型 - 二叉排序树中保存的元素所属类必须实现Comparable接口
- 定义内部类Node表示二叉排序树中的节点类
- 定义二叉排序树类,通过泛型由用户来指定元素类型,
-
操作二叉排序树
- 添加节点
-
遍历二叉排序树
遍历方式都有三种:
- 先序遍历:根左右 遍历结果:元素乱序
- 中序遍历:左根右 遍历结果:升序排列
- 后续遍历:左右根 遍历结果:元素乱序
-
重写toString方法,将中序遍历的结果返回
若树中为空,输出引用,结果为“[]”;
若树中有元素,输出引用,结果为[12,23,45,90],[]中的内容为中序遍历的结果思路:
1. 判断root是否为null,为null,返回[]
2. 否则,定义StringBuilder,初始值为[
2. 从root开始,调用中序遍历,将遍历到的元素拼接到builder中,且拼接上,
3. 遍历完成,需要将最后的,替换成],并将builder转换成String类型返回中序遍历代码:
注意点:中序遍历实际上是对某个目标节点遍历 -
根据元素查询节点
-
判断root是否为null
-为null,说明目标元素不存在,返回null
-不为null,从root开始判断是否为要找的目标节点
-判断目标节点的元素是否与查找的元素相等
- 若相等,返回当前节点
- 查找的元素<目标节点元素
- 目标节点的left上继续查找
- 判断left是否为null
- 为null,查找元素不存在,返回null
- 不为null,继续判断left是否为目标节点- 查找元素>目标节点元素 同理
-
-
删除节点
-
删除叶子节点:这种删除是最简单的删除,其操作为:
- 查询到元素对应的节点
- 让目标节点的上级节点指向它的引用置为null
-
删除只有一颗子树的节点:
让删除的目标节点的父节点指向其孙子节点 -
删除有两颗子树的节点:
让删除节点的前驱节点/后继节点来替换掉目标节点即可。前驱/后继节点是指中序遍历后,目标节点的前一个节点称为前驱节点;目标节点的后一个节点称为后继节点
前驱/后继节点不一定是叶子节点
-
-
极端情况下二叉排序树会发生什么?
极端情况下,会产生失衡二叉树
失衡二叉树不具备二叉排序树的优点(查询效率高)
所以不应该让失衡二叉树存在。
-
对失衡二叉树的优化
数据结构在设计时就考虑到失衡二叉树不应该存在的问题,所以数据结构中会对失衡二叉树进行了优化,不让失衡二叉树存在。
如何优化失衡二叉树?
对失衡二叉树的优化产生了另外两种新的数据结构,分别为
- AVL树(平衡二叉树)
- 红黑树 -
AVL树(平衡二叉树)
- 定义
是绝对平衡的二叉排序树,绝对平衡是指左右子树的高度差<=1
-
AVL树是如何保证绝对平衡状态的
通过旋转(左旋或右旋),AVL树中整个树,包括每颗子树都是绝对平衡的(左右子树高度差<=1)
-
定义
红黑树是实现了自平衡的二叉排序树,红黑树达到的相对平衡状态,而不是绝对平衡状态。相对平衡是指左右子树的高度差可以大于1(可能2,3,4),且红黑树中所有的节点颜色要么是黑色要么是红色。
-
红黑树中节点的颜色
红黑树中有2类节点的颜色是确定的
1. 根节点一定黑色的
2. 新添加的节点一定是红色的 -
红黑树是如何保证树达到相对平衡状态的
红黑树是通过旋转(左旋,右旋)和改变节点的颜色来保持树的平衡的
红黑树满足以下5大原则,则认为红黑树达到了相对平衡的状态- 节点是红色或黑色
- 根节点是黑色的
- 所有叶子节点是黑色(叶子节点是NIL节点)
- 每个红色节点的两个子节点都是黑色(从每个叶子到根的所有路径上不能有两个连续的红色节点) --红色节点不能连续出现
- 从任意一个节点到其每个叶子所经历的简单路径上包含的黑色节点的个数相同(简称黑高相同)
-
面试问到红黑树描述答案:
红黑树是实现了自平衡的二叉排序树,通过旋转和改变节点颜色保持达到相对平衡状态。旋转和改变节点颜色遵循5大原则:12345.。。。
-
红黑树的应用类:
-
TreeMap:其实就是红黑树
– 通过Debug的方式观察数据结构即可
– 输出引用观察结果
TreeMap中的toString方法调用了中序遍历,输出结果为根据key升序排列的结果 -
TreeSet:底层调用TreeMap,实质上是占用了TreeMap中key的那一列
- 输出引用,得到的是升序排列的结果
- TreeSet和TreeMap之间的关系:
TreeSet底层调用TreeMap
-
所有的set和map之间的关系:
HashMap – HashSet
TreeMap – TreeSet
LinkedHashMap – LinkedHashSet
注意点:set集合是不可重复集,不能说是无序的,因为有一些set的实现类是有序的,有一些是实现了排序的
HashSet – 无序的
TreeSet – 实现了排序效果的(升序/降序)
LinkedHashSet – 有序的 -
-
什么是散列表
散列表的底层其实是一个数组,这个数组叫做散列表,其核心原理为尽可能将元素分散开分布到数组的不同位置去。
在JDK1.8之前,散列表的底层为数组+链表结构
从JDK1.8开始,底层为数组+链表+红黑树 -
应用类:
HashMap – 就是散列表
hash 音译成哈希 翻译后:散列
-
相关面试题:描述HashMap,HashTable,ConcurrentHashMap的区别
HashTable是从JDK1.0开始的,是线程安全的
HashMap是从JDK1.2开始的,是非线程安全的
ConcurrentHashMap是JUC包下的,是线程安全的散列表,从JDK1.5版本开始的。
JUC(java.util.concurrent) 并发包区别:
-
HashMap是非线程安全的散列表,HashTable和ConcurrentHashMap都是线程安全的散列表,其中HashMap的key和value均允许为null;而HashTable和ConcurrentHashMap的key和value均不允许为null
-
HashTable和ConcurrentHashMap的区别为保证线程安全的方式是不同。
- HashTable是通过给整张散列表加锁来保证线程安全的,这种方式可以保证线程安全,但是并发执行效率非常低下。
- ConcurrentHashMap是如何保证线程安全的呢?
- JDK1.8之前,是通过分段锁机制来保证线程安全的
- 从JDK1.8开始,数据结构发生了,保证线程安全的方式也发生了改变,变为了通过乐观锁+Synchronized来保证线程安全
-
-
JDK1.8之前ConcurrentHashMap保证线程安全的方式
通过分段锁机制来保证线程安全
ConcurrentHashMap在JDK1.8之前数据结构与HashMap是不一样。
#### ConcurrentHashMap数据结构
-
保证线程安全的方式
JDK1.8之前,是通过分段锁机制来保证线程安全的
内部是一个长度为16的segment数组,每个segment数组中有一张散列表,当线程向某个segment进行update操作,会立即给segment加锁,从而保证线程安全。这种方式可以在保证线程安全的同时,一定程度上提高并发执行效率。
-
1.8中ConcruentHashMap是如何保证线程安全的
数据结构:从JDK1.8开始,其数据结构发生了改变,和1.8中的HashMap保持一致,为数组+链表+红黑树
保证线程安全的操作:是通过乐观锁+Synchronized配合作用来保证线程安全的。
-
乐观锁和悲观锁
乐观锁和悲观锁是两种思想,并不是真的锁,都是用于解决线程安全问题的。
悲观锁保证线程安全的方式:选择给目标对象加锁
乐观锁保证线程安全的方式:不给目标对象加锁,而是通过版本号机制来保证线程安全 -
悲观锁:
当多线程并发执行时,线程总是悲观的认为在自己执行期间一定有其他线程与之并发执行,会产生线程安全问题,所以为了保证线程安全,当线程访问目标资源时,选择立即给资源加锁,从而保证线程安全。
synchronized修饰同步资源(方法/代码块),会给对象加锁
synchronized是悲观锁的应用
-
乐观锁
乐观锁是指多线程并发执行时,线程总是乐观的认为,在自己执行期间,不会由其他线程与之并发执行,所以不会给目标对象加锁,但是实际上确实有可能有线程与之并发,产生线程安全问题,所以为了保证线程安全,采用版本号机制来保证线程安全。
- 乐观锁最终可以保证线程安全,但是不会采取给对象加锁的方式来保证线程安全,而是采用版本号机制。
- JDK1.8中ConcurrentHashMap的数据结构以及保证线程安全的方式
-
HashMap,HashTable和ConcurrentHashMap的区别
- HashMap是非线程安全的散列表,而另外两种都是线程安全的散列表,HashMap的key和value均可以为null,而另外两种的key和value均不可以为null
- HashTable和ConcurrentHashMap的区别为保证线程安全的方式不同:
HashTable是通过给整张散列表加锁的方式来保证线程安全的,这种方式并发执行效率非常低下
ConcurrentHashMap从JDK1.5版本开始出现的
在JDK1.8之前是通过分段锁机制来保证线程安全的。这种方式可以在保证线程安全的同时,一定程度上提高并发执行效率(当多线程并发访问不同的segment时,线程是完全并发的)
从JDK1.8开始其数据结构发生了改变,和JDK1.8中的HashMap的数据结构保持一致,保证线程安全的方式为乐观锁+synchroinized,当多线程并发向同一个散列桶添加元素时,若散列桶为空,则触发乐观锁机制,若不为空,则选择给桶中的头节点/根节点加锁来保证线程安全
-
定义
是指具有阻塞效果的队列,默认情况下,会从队尾入队操作,从队首进行出队操作
-
阻塞队列的作用
阻塞队列能够实现生产者和消费者的分离解耦,从而提高二者各自的执行效率
- 阻塞队列应用2:记录日志
-
BlockingQueue(接口) – 阻塞队列
- 创建阻塞队列对象
new LinkedBlockingQueue<>(int capacity)
new LinkedBlockingQueue<>(); - 入队
put(E):void 具有阻塞效果 - 出队
take():E 具有阻塞效果
- 注意:BlockingQueue是处于JUC(java.util.concurrent)包下的,是线程安全的
- 创建阻塞队列对象
-
题目:
-
模拟生产者线程和消费者线程并发执行,生产者阻塞的情况
注意:上述的生产者会一直不停的产生数据向队列中保存,消费者会一直从队列中取数据 思路:让生产者和消费者的速度不一致,让生产者产生数据的速度>消费者取数据的速度,则会出现生产者阻塞 public class ProductorBlockingDemo { public static void main(String[] args) { //创建阻塞队列 BlockingQueuequeue = new LinkedBlockingQueue<>(5); //创建生产者线程 Thread productor = new Thread(){ @Override public void run() { while (true){ try { queue.put("hello"+(int)(Math.random()*1000)); System.out.println(queue); } catch (InterruptedException e) { e.printStackTrace(); } } } }; //创建消费者线程 Thread consumer = new Thread(){ @Override public void run() { while (true){ try { Thread.sleep(2000); queue.take(); } catch (InterruptedException e) { e.printStackTrace(); } } } }; productor.start(); consumer.start(); } } -
模拟生产者和消费者并发执行,消费者阻塞的情况。
二者并发执行,当生产者产生数据的速度小于消费者取数据的速度时,就会出现消费者阻塞。 public class ConsumerBlockingDemo { public static void main(String[] args) { //创建阻塞队列 BlockingQueuequeue = new LinkedBlockingQueue<>(); //创建生产者线程 Thread productor = new Thread(){ @Override public void run() { while (true) { try { Thread.sleep(2000); queue.put((int) (Math.random() * 1000)); } catch (InterruptedException e) { e.printStackTrace(); } } } }; //创建消费者线程 Thread consumer = new Thread(){ @Override public void run() { while (true){ try { Integer ele = queue.take(); System.out.println(ele); } catch (InterruptedException e) { e.printStackTrace(); } } } }; productor.start(); consumer.start(); } }
-
笔试题:
写一个程序实现以下功能:一个生产者每一秒生成一个整型数字,放入一个队列。两个消费者消费数字(打印出数字),队列size大于等于10才能消费
注意点:两个消费者执行的操作是一样的,所以可以将消费者执行的任务单独定义成任务类,之后创建2个线程对象执行相同的任务即可。
思路:
1. 创建线程表示生产者线程,每隔1s产生数据存入队列
2. 创建任务类表示消费者线程执行的任务,任务为队列size大于等于10才能取出数据
3. 两次new Thread,创建2个消费者线程,且将任务对象放进去
4. 三个线程并发启动即可。
public static void main(String[] args) {
BlockingQueue queue =
new LinkedBlockingQueue<>();
//创建生产者线程
Thread productor = new Thread(){
@Override
public void run() {
while (true){
try {
Thread.sleep(1000);
queue.put((int)(Math.random()*3000));
System.out.println(queue);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
//创建任务类对象
Runnable task = new Runnable() {
@Override
public void run() {
while (true){
if (queue.size()>=10){
try {
queue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
};
//创建2个消费者线程对象
Thread consumer1 = new Thread(task);
Thread consumer2 = new Thread(task);
//三个线程并发启动
productor.start();
consumer1.start();
consumer2.start();
}
-
数据结构:
双向链表
递归
二叉排序树
红黑树
散列表
阻塞队列
-
排序算法
1.冒泡排序 --手写代码(高频)
2.选择排序
3.插入排序
4.希尔排序
5.归并排序
6.快速排序 -
二分查找算法
-
排序规则
相邻元素两两比较,大的/小的往后排,一轮比较结束,最大值出现在最大下标处。
-
练习:
1. 对int[]升序排列 2. 对int[]降序排列 3. 对String[]升序排列
-
选择排序
-
定义
选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。
选择排序:从第一位开始,为每一位求应该保存的最小值,该最小值则是从而排序部分选择出的。
-
练习:
1. 对int[]升序排列 2. 对int[]降序排列 3. 对String[]降序排列 引用类型数组若要实现排序效果,必须要求数组中的元素类型所属的类实现Comparable -
稳定性
稳定性是指:一个序列中相同的元素在排序后若元素的前后相对顺序没有发生改变,则排序算法是稳定的;反之若排序后,相同的前后顺序发生了改变,则排序算法是不稳定的。
在一趟选择,如果一个元素比当前元素小,而该小的元素又出现在一个和当前元素相等的元素后面,那么交换后稳定性就被破坏了。举个例子,序列5 8 5 2 9,我们知道第一遍选择第1个元素5会和2交换,那么原序列中两个5的相对前后顺序就被破坏了,所以选择排序是一个不稳定的排序算法
冒泡排序是稳定的。
-
-
插入排序
-
思想
插入排序,一般也被称为直接插入排序。对于少量元素的排序,它是一个有效的算法 [1] 。插入排序是一种最简单的排序方法,它的基本思想是将一个记录插入到已经排好序的有序表中,从而一个新的、记录数增1的有序表。
插入排序是稳定的排序算法
-
排序规则
将序列分成排好序和未排序部分,对未排序部分的元素进行挨个遍历,将遍历到的元素插入到排好序部分适当的位置去,直到未排序部分没有元素位置,排序完成。
-
-
练习:
1. 对int[]升序排列 2. 对int[]降序排列 3. 对String[]降序排列
-
冒泡,选择,插入排序的效率
冒泡排序是考察频率最高的排序算法,手写代码,但是排序效率是最低的。 通过实验得出三者的效率(耗时): 1. 数组长度100000 冒泡排序:13998 选择排序:3657 插入排序:1394 2. 数组长度200000 冒泡排序:56271 选择排序:14480 插入排序:5551
-
思想:
二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。
二分查找前提:
1. 要求线性表必须采用顺序存储结构 – 数组
2. 要求顺序存储的线性表实现了排序效果 -
线性表
线性表是最基本、最简单、也是最常用的一种数据结构。线性表(linear list)是数据结构的一种,一个线性表是n个具有相同特性的数据元素的有限序列。
线性表中数据元素之间的关系是一对一的关系,即除了第一个和最后一个数据元素之外,其它数据元素都是首尾相接的(注意,这句话只适用大部分线性表,而不是全部。比如,循环链表逻辑层次上也是一种线性表(存储层次上属于链式存储,但是把最后一个数据元素的尾指针指向了首位结点)。 -
顺序存储结构
顺序存储结构是存储结构类型中的一种,该结构是把逻辑上相邻的结点存储在物理位置上相邻的存储单元中,顺序存储结构的主要优点是节省存储空间。结点之间的逻辑关系由存储单元的邻接关系来体现。
-
查找方式:-- 折半查找
如何修改堆内存大小?
修改JVM堆大小方式:
找到Idea安装目录下的-->bin-->idea.exe.vmoptions
-server
-Xms128m //堆初始大小
-Xmx512m //最大堆内存
-XX:ReservedCodeCacheSize=240m
-XX:+UseConcMarkSweepGC //指定GC
-XX:SoftRefLRUPolicyMSPerMB=50-ea
-XX:CICompilerCount=2-Dsun.io.useCanonPrefixCache=false
-Djdk.http.auth.tunneling.disabledSchemes=""
-XX:+HeapDumpOnOutOfMemoryError
-XX:-OmitStackTraceInFastThrow
-Djdk.attach.allowAttachSelf=true
-Dkotlinx.coroutines.debug=off
-Djdk.module.illegalAccess.silent=true
堆配置
-Xms:初始堆大小
-Xmx:最大堆大小
-XX:NewSize=n: 设置年轻代大小
-XX:NewRatio=n: 设置年轻代和年老代的比值,
如:为3表示年轻代和年老代比值为1:3,年轻代占堆内存的1/4
-XX:SurvivorRatio=n: 年轻代中Eden区与两个Survivor区的比值。
注意Survivor区有两个。如3表示Eden:3 Survivor:2, 一个Survivor区占整个年轻代的1/5
-XX:MaxPermSize=n:设置永久代大小
永久代:元空间
JVM调优以及GC常用算法
-
JVM调优的目的就是降低full gc的触发频率
-
调优措施
- 扩大整个堆内存
- 尽可能的让对象在年轻代中被回收,不进入老年代
- 扩大Eden区内存
- 扩大整个堆内存 – 建议
- 调整Eden和两个S区的比例 – 不是长久之计,可以临时/紧急使用
- 扩大Eden区内存
- 将GC由CMSGC更换为G1GC
-
GC的更迭
SerialleGC GC应用于单线程的
PraralleGC GC应用于线程之间并行的
CMSGC -ConcurrentMarkSweepGC 应用于多线程并发场景的
CMSGC是从JDK1.5版本开始的默认GC,包括JDK1.8
G1GC 1:frist 从JDK1.9开始的默认的GC -
G1GC
G1GC使用时,堆内存结构不再和JDK1.8中相同,会变成以块为单位进行存储数据,分代思想依然使用。
-
如何修改堆内存大小?
修改JVM堆大小方式:
找到Idea安装目录下的-->bin-->idea.exe.vmoptions
-server
-Xms128m //堆初始大小
-Xmx512m //最大堆内存
-XX:ReservedCodeCacheSize=240m
-XX:+UseConcMarkSweepGC //指定GC
-XX:SoftRefLRUPolicyMSPerMB=50-ea
-XX:CICompilerCount=2-Dsun.io.useCanonPrefixCache=false
-Djdk.http.auth.tunneling.disabledSchemes=""
-XX:+HeapDumpOnOutOfMemoryError
-XX:-OmitStackTraceInFastThrow
-Djdk.attach.allowAttachSelf=true
-Dkotlinx.coroutines.debug=off
-Djdk.module.illegalAccess.silent=true
堆配置
-Xms:初始堆大小
-Xmx:最大堆大小
-XX:NewSize=n: 设置年轻代大小
-XX:NewRatio=n: 设置年轻代和年老代的比值,
如:为3表示年轻代和年老代比值为1:3,年轻代占堆内存的1/4
-XX:SurvivorRatio=n: 年轻代中Eden区与两个Survivor区的比值。
注意Survivor区有两个。如3表示Eden:3 Survivor:2, 一个Survivor区占整个年轻代的1/5
-XX:MaxPermSize=n:设置永久代大小
永久代:元空间
GC常用算法
1. 复制算法 -- minor gc使用的
2. 标记-清除算法 缺点:清除后,可用内存不连续导致内存利用率低下
3. 标记-整理算法 --full gc使用该算法
特点:清除后会将可用的内存整理到一起,最终内存是连续可用的,利用率高
数据库
-
数据库有两张表为user表和user_role表,执行:SELECT u.*,r.name FROM USER u LEFT JOIN user_role r ON u.id = r.user_id 之后获取到的结果为:(A)
left join/right join:主表的所有数据都显示 - 主表:left join 左表为主表; - right join:右表为主表 (inner) join:内连接查询,特点:查询满足所有条件的数据 user表数据: user_role表数据: id username id name user_id 1 张三 1 售后 1 2 李四 2 运维 2 3 王五 3 研发 3 4 小刘 4 实施 5 A. 1 张三 售后 2 李四 运维 3 王五 研发 4 小刘 null B. 1 张三 售后 2 李四 运维 3 王五 研发 C. 1 张三 售后 2 李四 运维 3 王五 研发 null null 实施
-
从表t1(有字段id,name,sex,score)中取出sex为男,score列前十名的name字段
select t1.name,t1.score from t1 join (select distinct score from t1 order by score desc limit 10)tmp on t1.score=tmp.score where t1.sex='男' 注意点:考虑到前10条数据中分数有重复的,则需要先去重,然后再筛选前10条数据 distinct --对结果集(结果集可以是多个字段)进行去重 distinct 后可以加一个或多个字段
-
请描述查询语句中各部分的执行顺序
select distinct...from t1 (left/right) join t2 on t1.xx=t2.xx where t1.xx=? and t2.xx=? group by t1.xx having ...order by ... limit .. 1. from join 产生虚拟表v1 - 产生的是笛卡尔积 笛卡尔积:两表完全连 接的结果叫做笛卡尔积,会产生m*n条数据 2. on 根据连接条件进行筛选,基于V1,产生虚拟表v2 3. left/right join 若是外连接,将主表中的所有数据进行补充到v2表中 4. where 根据查询条件,从v2中进行数据筛选,产生虚拟表v3 5. group by 对v3分组,产生虚拟表v4 经常会和聚合函数配合使用,聚合函数的结果在group by之后就会产生 6. having 对分组后的结果再次筛选 ,产生虚拟表v5 7. select 选择最终要显示的字段,产生虚拟表v6 8. distinct 对结果集进行去重,产生虚拟表v7 9. order by 对结果根据某字段进行升序/降序排列 10. limit 选择需要的数据 where 和having区别: where是在分组之前进行数据筛选,having是对分组后的结果再次筛选 where,group by,having若同时出现,则三者的顺序一定是 where在 group by之前,having在group by之后
- 表名:stu_score
| id | name | score | bj |
|---|---|---|---|
| 1 | 赵一 | 89 | 1班 |
| 2 | 钱二 | 88 | 2班 |
| 3 | 孙三 | 84 | 1班 |
| 4 | 李四 | 86 | 2班 |
| 5 | 王五 | 87 | 1班 |
| 6 | 吴六 | 91 | 2班 |
获取每个班级的最后一名
1. 统计出每个班级的最低分 2. 和stu_Score表进行联查,连接条件为班级相等,且分数等于统计出的最低分 select ss.* from stu_score ss join (select bj,min(score) min from stu_score group by bj)tmp on ss.bj=tmp.bj and ss.score=tmp.min 同学的答案: select id,name,min(score) as score,bj from stu_score group by bj --是否正确? 错误的 group by后能查询得到的字段有哪些? group by后可以查询到的正确字段有: 1. 分组的字段,和分组字段有一对一关系的字段 2. 根据该字段分组后的聚合函数结果
-
表user,字段有:id(非自增,不连续),age,name,sex,addTime。求创建时间(addTime)在2020-05-22至2020-06-17之间所有数据
考点:筛选某段时间之间的数据,可以使用between。。and。。。也可以使用大于或小于 select * from user where addTime between '2020-05-22' and '2020-06-17' --建议 或 select * from user where addTime>='2020-05-22' and addTime<='2020-06-17' 注意点:数据库中的between。。。and。。两个边界值是都包含的 而在Java中,若出现两个边界值,通常都是包头不包尾
-
基于表user,查出表中第10条至第20条连续分页数据的sql语句
select * from user limit 9,11
student表:
| sno | sname | sage |
|---|---|---|
| 1 | 周杰伦 | 18 |
| 2 | 周润发 | 18 |
| 3 | 吴孟达 | 25 |
| 4 | 刘德华 | 25 |
| 5 | 李连杰 | 29 |
| cno | cname |
|---|---|
| 1 | 语文 |
| 2 | 数学 |
| 3 | 英语 |
| sno | cno | score |
|---|---|---|
| 1 | 1 | 60 |
| 1 | 2 | 61 |
| 2 | 1 | 80 |
三张表分别为:student ,course,score
-
查询所有学生的学号,姓名,选课数,总成绩
思路:student和score进行外连接查询,其中student为主表,需要注意统计每个学生的选课数和总成绩时,需要根据学号分组,这里必须根据主表的学号分组。 ifnull(exp1,exp2):若exp1为null,则返回exp2的结果;反之若exp1不为null,则返回exp1的结果 select s.sno,sname,count(cno)'选课数',ifnull(sum(score),0) '总成绩' from student s left join score sc on s.sno=sc.sno group by s.sno
-
查询没有学完所有课程的学生学号,姓名
题意:查找没有选修全部课程的学生 思路: 1. 统计每个学生的选课数,然后筛选选课数<总课程数的学生 2. 总课程数需要从课程表中查询 select count(*) from course1 count()中的参数: 1. 可以为* ,表示统计行 2. 可以为字段名,表示对该字段中的非null值进行计数 这里会出现对分组后的结果再次进行筛选,需要使用having select s.sno,sname,count(cno) cou from student s left join score sc on s.sno=sc.sno group by s.sno having cou<(select count(*) from course1)
班级表(class)
| 编号 | 班级名称 |
|---|---|
| id | class_name |
| 1 | 一班 |
| 2 | 二班 |
| 3 | 三班 |
| 4 | 四班 |
| 5 | 五班 |
学生表(student)
| 编号 | 学号 | 姓名 | 性别 | 所属班级 |
|---|---|---|---|---|
| id | stu_no | stu_name | stu_gender | class_id |
| 1 | 2020001 | 张三 | 男 | 1 |
| 2 | 2020002 | 李四 | 男 | 1 |
| 3 | 2020003 | 李丽 | 女 | 2 |
| 4 | 2020004 | 赵婷 | 女 | 3 |
| 5 | 2020005 | 王五 | 男 | 3 |
成绩表(score)
| 学生 | 语文 | 数学 |
|---|---|---|
| stu_id | chinese | math |
| 1 | 70 | 47 |
| 2 | 80 | 60 |
| 3 | 50 | 82 |
| 4 | 80 | 90 |
业务场景限制:
- 一个班级有多名学生,一名学生只属于一个班级
- 学生有可能没有成绩
-
查询所有学生的信息(学号,姓名,性别,班级名称)
select stu_no,stu_name,stu_gender,class_name from student s join class c on s.class_id=c.id
-
查询所有人(包括没有成绩的学生)的课程分数(学号,姓名,性别,班级名称,语文分数,数学分数)
学生表和班级表是内连接查询,学生表和成绩表是外连接查询 语法: 三表连接,若有内连且有外联,语法为: select ...from a join b on a.xx=b.xx left join c on a.xx=c.xx 不要写成: select...from a join b left join c on a.xx=b.xx and a.xx=c.xx 但是,若三表全都内联,此时语法应为: select..from a join b join c on a.xx=b.xx and a.xx=c.xx select stu_no,stu_name,stu_gender,class_name,chinese,math from student s join class c on s.class_id=c.id left join score sc on s.id=sc.stu_id
-
查询语文分数比“张三”高的学生(学号,姓名,性别,班级名称,语文分 数)
1. 查询到张三的语文分数 select chinese from student s join score sc on s.id=sc.stu_id where stu_name='张三' 2. 查询语文分数高于张三语文的学生 select stu_no,stu_name,stu_gender,class_name,chinese from student s join class c join score sc on s.id=sc.stu_id and s.class_id=c.id where sc.chinese>(select chinese from student s join score sc on s.id=sc.stu_id where stu_name='张三')
-
查询各科都合格(分数>=60)的学生(学号,姓名,语文分数,数学分数)
select stu_no,stu_name,chinese,math from student s join score sc on sc.stu_id=s.id where chinese>=60 and math>=60
-
查询班级人数>=2的班级(班级编号,班级名称,人数)
思路: 1. 统计出每个班级的人数(班级表和学生表内连接查询) 2. 在统计出的结果中再次筛选人数大于等于2的班级 select c.id,class_name,count(s.id) cou from class c join student s on c.id=s.class_id group by s.class_id having cou>=2
| name | course | score |
|---|---|---|
| 张三 | 语文 | 81 |
| 张三 | 数学 | 75 |
| 李四 | 语文 | 76 |
| 王五 | 语文 | 81 |
| 王五 | 数学 | 100 |
| 王五 | 英语 | 90 |
| id | name |
|---|---|
| 1 | 张三 |
| 2 | 张三 |
| 3 | 李四 |
| 4 | 王五 |
| 5 | 王五 |
| 6 | 王五 |
要求只留下:2 张三, 3 李四, 6 王五 这三条记录
6. 学生表如下| 自动编号 | 学号 | 姓名 | 课程编号 | 课程名称 | 分数 |
|---|---|---|---|---|---|
| 1 | 2021001 | 张三 | 0001 | 数学 | 69 |
| 2 | 2021002 | 李四 | 0001 | 数学 | 89 |
| 3 | 2021001 | 张三 | 0001 | 数学 | 69 |
- 删除除了自动编号不同,其它都相同的学生冗余信息
(1) 用户表中添加一条数据,请写出SQL语句
(2) 在用户表中把姓名为"李四"的在职用户的年龄修改为28
(3) 查询所有的角色名称为"技术人员",性别是女的在职人员
(4) 把角色名称为"研发人员"的用户"张三"从用户表中删除
(5) 查询年龄在18到24岁之间的男性在职人员
(6)查询年龄为18,19,20的男性和女性员工的人数,结果如下图所示:
| 年龄 | 男 | 女 |
|---|---|---|
| 18 | 5 | 3 |
| 19 | 4 | 6 |
| 20 | 1 | 0 |
(7) 查询前50名姓张的男员工
(8)查询所有根角色员工,根角色就是父角色为null的角色.
9. 以下四个表,表名称及表结构如下:student(sno,sname,sage,ssex) 学生表 course(cno,cname,tno) 课程表 sc(sno,cno,score) 成绩表 teacher(tno,tname) 教师表
- 查询课程1的成绩比课程2的成绩高的所有学生的信息
- 查询平均成绩大于60分的同学的学号和平均成绩
- 查询学过‘李四’老师所教课程的所有同学的学号,姓名
- 查询姓“李”得老师的个数
- 查询每门课程的选修人数(课程名称,学生数量)–存在没有人选的课程
- 删除“1002”同学的“1”课程的成绩
- 查询选修人数最多的课程(课程id,课程名称,学生数量)–考虑有多门课程都是选修最多的情况
| 账号 | 消费 | 时间 | 金额 | 班次 |
|---|---|---|---|---|
| accounts | details | date | money | class |
| s0001 | 房费 | 2020-01-01 | 280 | 001 |
| s0001 | 酒水 | 2020-01-02 | 120 | 001 |
| s0001 | 房费 | 2020-01-08 | 300 | 003 |
| s0002 | 酒水 | 2020-01-29 | 50 | |
| s0003 | 房费 | 2020-01-31 | 180 | 002 |
| s0004 | 房费 | 2020-02-01 | 230 | 001 |
| s0005 | 酒水 | 2020-02-01 | 100 | |
| s0005 | 房费 | 2020-02-02 | 128 | 001 |
- 查询出房费都大于200的账号
- 查询出1月份每个账号酒水和房费的总金额
- 将不是房费的班次都更改为‘001’
- 查询出消费都大于100的账号
下面是学生成绩表(student_score)结构说明
| 字段名称 | 字段解释 | 字段类型 | 字段长度 |
|---|---|---|---|
| student_id | 学号 | 字符 | 8 |
| student_name | 姓名 | 字符 | 50 |
| student_gender | 性别 | 字符(男/女) | 4 |
| course_id | 课程号 | 字符 | 5 |
| score | 分数 | 数值 | 3 |
| ismakeup | 当前考试是否为补考 | 字符(补考:1;非补考:0) | 2 |
下面是课程表(course)说明
| 字段名称 | 字段解释 | 字段类型 | 字段长度 | 约束 |
|---|---|---|---|---|
| course_id | 课程号 | 字符 | 5 | PK |
| course_name | 课程名 | 字符 | 30 | Not null |
| course_desc | 课程介绍 | 字符 | 60 |
1、查找第一次考试后所有需要补考(小于60分)的学生姓名和这门课程的名称和成绩;
2、查询每个学生第一次考试后需要补考(小于60分)的课程平均分和科目数
3、查询所有参加了补考的学生的学生姓名,课程名称,补考成绩和非补考成绩;
6. 有如下表:emp:
empno int(员工编号) ,ename varchar(50)(员工姓名) ,job varchar(100) (工作岗位),mgr int (上级领导编号),hiredate date(雇佣日期),sal int(薪金),comm int(佣金) deptno int (部门编号)
提示:工资=薪金+佣金
dept表:
deptno int (部门编号) , dname 部门名称 loc 地点
- 列出在每个部门工作的员工数量,平均工资
- 列出所有员工的姓名,部门名称和工资
- 列出所有部门的详细信息和部门人数
- 列出各种工作的最低工资
- 列出各个部门的manager的最低薪金(若是manager,其job的值为manageer)
- 列出受雇日期早于其直接上级的所有员工
- 列出部门名称和这些部门的员工信息,同时列出那些没有员工的部门
- 列出所有‘clerk’(办事员)岗位的姓名以及部门名称
- 列出最低薪金大于6500的各种工作
- 列出在研发部工作的员工的姓名,假定不知道研发部的部门编号
| name | course | score |
|---|---|---|
| 张三 | 语文 | 81 |
| 张三 | 数学 | 75 |
| 李四 | 语文 | 76 |
| 王五 | 语文 | 81 |
| 王五 | 数学 | 100 |
| 王五 | 英语 | 90 |
思路:统计每个学生的最低分,然后筛选最低分大于等于80的学生 select name from score group by name having min(score)>=805. 有一张表student,包括字段id和name,请写一条sql语句,将表中name字段中重复的记录删除,只保留重复数据中的id最大的那一条数据。
| id | name |
|---|---|
| 1 | 张三 |
| 2 | 张三 |
| 3 | 李四 |
| 4 | 王五 |
| 5 | 王五 |
| 6 | 王五 |
要求只留下:2 张三, 3 李四, 6 王五 这三条记录
思路:1. 统计出每个name的最大id 2. 删除表中数据,除了以上查询出的最大id那些之外 delete from student1 where id not in (select max(id) from student1 group by name) 若以上sql执行报错,检查数据库版本是否为mariadb10版本,低于该版本,不支持以上sql6. 学生表如下
| 自动编号 | 学号 | 姓名 | 课程编号 | 课程名称 | 分数 |
|---|---|---|---|---|---|
| 1 | 2021001 | 张三 | 0001 | 数学 | 69 |
| 2 | 2021002 | 李四 | 0001 | 数学 | 89 |
| 3 | 2021001 | 张三 | 0001 | 数学 | 69 |
-
删除除了自动编号不同,其它都相同的学生冗余信息
group by 后可以加多个字段 思路: 1. 根据除了自动编号外的其他所有字段进行统计,统计一组相同数据的最大id/最小id select max(id) from student2 group by stu_no,name,course_id,course_name,score 2. 在第一步的基础上进行删除 delete from student2 where id not in (select max(id) from student2 group by stu_no,name,course_id,course_name,score) 第七题+休息共20分钟,10:05上课
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R2ysUHcx-1655191531215)(D:就业冲刺final-2112day061.png)]
case when 表达式
用法: case when 表达式 then exp1
when 表达式 then exp2
when 表达式 then exp3
else exp4 end
案例:成绩表中根据分数所处区间不同,输出以下格式数据
name course level
xx xxx 优秀
select name,course,
(case when score>=90 and score<=100 then '优秀'
when score>=80 and score<90 then '良好'
when score>=70 and score<80 then '中等'
when score>=60 and score<70 then '及格'
else '不及格' end) level from score
经常考察的用法:case when和聚合函数(sum(...))配合使用
select name,
sum(case when score>=60 then 1 else 0 end)'及格科目数',
sum(case when score<60 then 1 else 0 end)'不及格科目数'
from score group by name
case when和count()聚合函数配合使用:
select name,
count(case when score>=60 then 1 end)'及格科目数',
count(case when score<60 then 1 end)'不及格科目数'
from score group by name
使用sum和count,case when的写法是不一样的
sum():case when若返回1,表示+1,若返回0,表示+0
count():case when不论返回什么,只要不是null,就进行一次计数,只有返回null,才不计数,所以若要将sum换成count,case when部分需要改变,变为:
count(case when...then 1 end) 没有else表示不满足条件返回null
比赛表答案:
select match_date '比赛日期',
sum(case when result='胜' then 1 else 0 end) '胜',
sum(case when result='负' then 1 else 0 end) '负'
from match_record group by match_date
8. 有如下3张表,完成如下需求:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8LAs8OIu-1655191531215)(D:就业冲刺final-2112day062.png)]
(1) 用户表中添加一条数据,请写出SQL语句
insert into user values(1,'tom','男',21,'2022-12-12',1)
(2) 在用户表中把姓名为"李四"的在职用户的年龄修改为28
update user set age=28 where name='李四' and state=1; update user set age=28,sex='女' where..... -- 注意点:若修改多个值,值之间用逗号连接
(3) 查询所有的角色名称为"技术人员",性别是女的在职人员
select u.* from role r join user_role ur join user u on r.id=ur.role_id and ur.user_id=u.id where r.name='技术人员' and sex='女' and state=1; 考点:多表内连接查询 注意点:能用联查尽量用联查,不要用嵌套查询,因为嵌套查询会产生中间表,查询效率相对于联查会低。
(4) 把角色名称为"研发人员"的用户"张三"从用户表中删除
1. 查询角色名研发人员的用户张三的用户id select u.id from role r join user_role ur join user u on r.id=ur.role_id and ur.user_id=u.id where r.name='研发人员' and u.name='张三' 2. 从用户表中根据id删除数据 delete from user where id in (select u.id from role r join user_role ur join user u on r.id=ur.role_id and ur.user_id=u.id where r.name='研发人员' and u.name='张三')
(5) 查询年龄在18到24岁之间的男性在职人员
select * from user where age between 18 and 24 and sex='男' and state=1
(6)查询年龄为18,19,20的男性和女性员工的人数,结果如下图所示:
| 年龄 | 男 | 女 |
|---|---|---|
| 18 | 5 | 3 |
| 19 | 4 | 6 |
| 20 | 1 | 0 |
having 对分组后的数据进行筛选 本题在分组之前,先筛选18-20之间的数据,使用where 思路: 1. 从user表中先筛选年龄处于18-20之间的数据 2. 针对以上数据统计每个年龄的男女人数,(在分组过程中统计那男女人数,使用case when) select age '年龄', sum(case when stu_gender='男' then 1 else 0 end)'男', sum(case when stu_gender='女' then 1 else 0 end)'女' from student where age between 18 and 20 group by age
(7) 查询前50名姓张的男员工
select * from user where name like '张%' and sex='男' limit 50;
(8)查询所有根角色员工,根角色就是父角色为null的角色.
select u.* from role r join user_role ur join user u on r.id=ur.role_id and ur.user_id=u.id where parentId is null9. 以下四个表,表名称及表结构如下:
student(sno,sname,sage,ssex) 学生表 course(cno,cname,tno) 课程表 sc(sno,cno,score) 成绩表 teacher(tno,tname) 教师表
-
查询课程1的成绩比课程2的成绩高的所有学生的信息
思路: 本题使用表的自连接查询,将成绩表当成2份,一份作为课程1的成绩表,一份作为课程2的成绩表,两表联查,连接条件为学号相等,则能查询即有课程1成绩又有课程2成绩的学生,连接条件还有一个为:课程1的成绩高于课程2的成绩,最后查询到的学号即为要查找的学生,再根据学号到学生表查询数据即可。 select s.*,sc1.score '课程1分数',sc2.score '课程2分数' from sc sc1 join sc sc2 join student s on sc1.sno=sc2.sno and sc1.score>sc2.score and sc1.sno=s.sno where sc1.cno=1 and sc2.cno=2
-
查询平均成绩大于60分的同学的学号和平均成绩
select sno,avg(score) av from sc group by sno having av>60 小数点后截取操作的函数:(数据库中提供了不止一个函数) round(col,2) - 四舍五入
-
查询学过‘李四’老师所教课程的所有同学的学号,姓名
select distinct s.sno,sname from teacher t join course c join sc join student s on t.tno=c.tno and c.cno=sc.cno and sc.sno=s.sno where tname='李四'
-
查询姓“李”得老师的个数
select count(*) from teacher where tname like '李%' count()如果为*,按行统计的;若为字段,按字段中的非null值进行统计
-
查询每门课程的选修人数(课程名称,学生数量)–存在没有人选的课程
注意:没有人选的课程也要显示,所以课程表为主表,和sc进行外连接 select cname,count(sno) from course c left join sc on c.cno=sc.cno group by c.cno
-
删除“1002”同学的“1”课程的成绩
delete from sc where cno=1 and sno='1002'
-
查询选修人数最多的课程(课程id,课程名称,学生数量)–考虑有多门课程都是选修最多的情况
同学的思路: 查询每门课程的选修人数,根据人数降序排列,limit 1直接筛选出选修人数最多的课程 - 不可用,因为可能存在有些课程的选修人数相同的情况 正确思路: 1. 查询出选修人数最多的值是多少 统计每门课程的选修人数,根据人数降序排列,选择第一条数据,即为最多的人数 select count(sno) cou from sc group by cno order by cou desc limit 1 2. 统计每门课程的选修人数,筛选人数=上述最多的人数 select c.cno,cname,count(sno) cou from sc join course c on sc.cno=c.cno group by c.cno having cou=(select count(sno) cou from sc group by cno order by cou desc limit 1)
| 账号 | 消费 | 时间 | 金额 | 班次 |
|---|---|---|---|---|
| accounts | details | date | money | class |
| s0001 | 房费 | 2020-01-01 | 280 | 001 |
| s0001 | 酒水 | 2020-01-02 | 120 | 001 |
| s0001 | 房费 | 2020-01-08 | 100 | 003 |
| s0002 | 酒水 | 2020-01-29 | 50 | |
| s0003 | 房费 | 2020-01-31 | 180 | 002 |
| s0004 | 房费 | 2020-02-01 | 230 | 001 |
| s0005 | 酒水 | 2020-02-01 | 100 | |
| s0005 | 房费 | 2020-02-02 | 128 | 001 |
-
查询出房费都大于200的账号
题意:查询每个账号每笔房费都大于200的账号 思路: 1. 筛选所有的房费数据 --- where 2. 统计每个账号房费的最低消费金额,筛选最低消费金额大于200的账号--having select accounts from guest where details='房费' group by accounts having min(money)>200
-
查询出1月份每个账号酒水和房费的总金额
如何筛选1月份数据: 1. where date between '2020-01-01' and '2020-01-31' 2. 日期函数: year(col) -- 获取日期中的年份值 month(col) -- 获取日期中的月份值 day(col) --获取日期中的具体号数 hour(col) -- 获取日期中的小时 前提:col的类型是datetime/time minute(col) -- 获取分钟数 second(col) -- 获取秒数 where month(date)=1 select accounts,sum(money) from guest where month(date)=1 group by accounts
-
将不是房费的班次都更改为‘001’
!= <> update guest set class='001' where details!='房费'
-
查询出消费都大于100的账号
思路:统计每个账号的最低消费金额,筛选最低消费金额大于100的账号 select accounts from guest group by accounts having min(money)>100
下面是学生成绩表(student_score)结构说明
| 字段名称 | 字段解释 | 字段类型 | 字段长度 |
|---|---|---|---|
| student_id | 学号 | 字符 | 8 |
| student_name | 姓名 | 字符 | 50 |
| student_gender | 性别 | 字符(男/女) | 4 |
| course_id | 课程号 | 字符 | 5 |
| score | 分数 | 数值 | 3 |
| ismakeup | 当前考试是否为补考 | 字符(补考:1;非补考:0) | 2 |
下面是课程表(course)说明
| 字段名称 | 字段解释 | 字段类型 | 字段长度 | 约束 |
|---|---|---|---|---|
| course_id | 课程号 | 字符 | 5 | PK |
| course_name | 课程名 | 字符 | 30 | Not null |
| course_desc | 课程介绍 | 字符 | 60 |
1、查找第一次考试后所有需要补考(小于60分)的学生姓名和这门课程的名称和成绩;
select student_name,course_name,score from student_score ss join course c on ss.course_id=c.course_id where is_makeup=0 and score<60
2、查询每个学生第一次考试后需要补考(小于60分)的课程平均分和科目数
思路: 1. 筛选第一次考试后需要补考的数据 2. 再以上的数据中统计每个学生不及格的科目书和平均分 select student_id,student_name,count(course_id),avg(score) from student_score where is_makeup=0 and score<60 group by student_id
3、查询所有参加了补考的学生的学生姓名,课程名称,补考成绩和非补考成绩;
思路: 使用表的自联结查询,将成绩表当成2份,一份作为补考成绩表,一份作为非补考成绩表,两表联查,联查条件为:学号相等且课程编号相同(这样就能查到某个学生某门课程的非补考成绩和补考成绩),再和course联查,获取课程名称 select sc1.student_name,course_name,sc1.score '补考成绩',sc2.score '非补考成绩' from student_score sc1 join student_score sc2 join course c on sc1.student_id=sc2.student_id and sc1.course_id=sc2.course_id and c.course_id=sc1.course_id where sc1.is_makeup=1 and sc2.is_makeup=06. 有如下表:
emp:
empno int(员工编号) ,ename varchar(50)(员工姓名) ,job varchar(100) (工作岗位),mgr int (上级领导编号),hiredate date(雇佣日期),sal int(薪金),comm int(佣金) deptno int (部门编号)
提示:工资=薪金+佣金
dept表:
deptno int (部门编号) , dname 部门名称 loc 地点
- 列出在每个部门工作的员工数量,平均工资
- 列出所有员工的姓名,部门名称和工资
- 列出所有部门的详细信息和部门人数
- 列出各种工作的最低工资
- 列出各个部门的manager的最低薪金(若是manager,其job的值为manageer)
- 列出受雇日期早于其直接上级的所有员工
- 列出部门名称和这些部门的员工信息,同时列出那些没有员工的部门
- 列出所有‘clerk’(办事员)岗位的姓名以及部门名称
- 列出最低薪金大于6500的各种工作
- 列出在研发部工作的员工的姓名,假定不知道研发部的部门编号
1、查询"语文"课程比"数学"课程成绩高的学生的性别及课程分数
2、查询"语文"课程比"数学"课程成绩低的学生的性别及课程分数 3、查询平均成绩大于等于60分的同学的学生编号和学生姓名和平均成绩
4、查询平均成绩小于60分的同学的学生编号和学生姓名和平均成绩(包括有成绩的和无成绩的)
5、查询所有同学的学生编号、学生姓名、选课总数、所有课程的总成绩
6、查询"王"姓老师的数量
7、查询学过"张三"老师授课的同学的信息
8、查询学过编号为"01"并且也学过编号为"02"的课程的同学的信息
9、按平均成绩从高到低显示所有学生的所有课程的成绩以及平均成绩
10、查询各科成绩最高分、最低分和平均分:以如下形式显示:课程ID,课程name,最高分,最低分,平均分,及格率,中等率,优良率,优秀率 (及格为>=60,中等为:70-80,优良为:80-90,优秀为:>=90)
11、查询每门科目成绩都在60分及以上的学生id,姓名,成绩
12、查询至少有一门科目成绩在90分以上的学生id,姓名,成绩
13、查询所有科目成绩总和在240分及以上的学生id,姓名,和成绩总和,降序排列
14、查询每个科目成绩最高的学生的学生id,姓名,写在一个表里
15、查询每个科目成绩最低的学生的学生id,姓名
16、查询目前没有安排课程的老师姓名
17、将之前老师表的英文名字转化为大写
18、查询学生表中重复的姓名
19、统计各科成绩各分数段人数:课程编号,课程名称,[100-85],[85-70],[70-60],[0-60]及所占百分比
6. 有如下表:emp:
empno int(员工编号) ,ename varchar(50)(员工姓名) ,job varchar(100) (工作岗位),mgr int (上级领导编号),hiredate date(雇佣日期),sal int(薪金),comm int(佣金) deptno int (部门编号)
提示:工资=薪金+佣金
dept表:
deptno int (部门编号) , dname 部门名称 loc 地点
-
列出在每个部门工作的员工数量,平均工资
考点: 1. 以部门表为主表,使用外连接查询 2. avg()中需要填写的是两列之和 salary+comm select d.id,d.name,count(e.id),round(avg(salary+comm),2) from dept d left join employee e on e.dept_id=d.id group by d.id
-
列出所有员工的姓名,部门名称和工资
select e.name,d.name,salary+comm '工资' from employee e join dept d on e.dept_id=d.id
-
列出所有部门的详细信息和部门人数
select d.*,count(e.id) from dept d left join employee e on d.id=e.dept_id group by d.id count()中*和字段的效果是不一样的,一些场景效果可能相同,但是意义不同
-
列出各种工作的最低工资
select job,min(salary+comm) from employee group by job
-
列出各个部门的manager的最低薪金(若是manager,其job的值为manageer)
select dept_id,min(salary) from employee where job='manager' group by dept_id
-
列出受雇日期早于其直接上级的所有员工
考点:表的自连接查询 select e1.*,e2.hiredate '上级领导的入职日期' from employee e1 join employee e2 on e1.mgr_id=e2.id where e1.hiredate
-
列出部门名称和这些部门的员工信息,同时列出那些没有员工的部门
select d.id,d.name,e.id,e.name,dept_id from dept d left join employee e on d.id=e.dept_id order by d.id
-
列出所有‘clerk’(办事员)岗位的姓名以及部门名称
select e.name,d.name from employee e join dept d on e.dept_id=d.id where job='clerk'
-
列出最低薪金大于6500的各种工作
1. 先统计每种工作的最低薪资--分组 2. 筛选最低薪金大于6500 select job from employee group by job having min(salary)>6500
-
列出在研发部工作的员工的姓名,假定不知道研发部的部门编号
select e.name,d.name from dept d join employee e on d.id=e.dept_id where d.name='研发部'
1、查询"语文"课程比"数学"课程成绩高的学生的性别及课程分数
1. 查询出语文的成绩表 select sc.* from course c join score sc on c.c_id=sc.c_id where c_name='语文' 2. 查询出数学的成绩表 select sc.* from course c join score sc on c.c_id=sc.c_id where c_name='数学' 3. 语文成绩表和数学成绩表以及学生表联查,获取满足条件的学生数据 select s.*,chinese.s_score '语文成绩',math.s_score '数学成绩' from student s join (select sc.* from course c join score sc on c.c_id=sc.c_id where c_name='语文') chinese join (select sc.* from course c join score sc on c.c_id=sc.c_id where c_name='数学') math on chinese.s_id=math.s_id and chinese.s_score>math.s_score and chinese.s_id=s.s_id
2、查询"语文"课程比"数学"课程成绩低的学生的性别及课程分数
3、查询平均成绩大于等于60分的同学的学生编号和学生姓名和平均成绩
本题不能使用avg聚合函数求平均分,因为已经提示包括有成绩和无成绩的,所以平均分需要这样求:总分/总课程数 思路: 学生表和成绩表外连接查询,获取每个学生的平均分,然后筛选平均分大于等于60的学生数据 select s.*,sum(s_score)/(select count(*) from course) av from student s left join score sc on s.s_id=sc.s_id group by s.s_id having av>=60
4、查询平均成绩小于60分的同学的学生编号和学生姓名和平均成绩(包括有成绩的和无成绩的)
5、查询所有同学的学生编号、学生姓名、选课总数、所有课程的总成绩
select s.*,count(c_id)'选课总数',ifNull(sum(s_score),0) '总成绩' from student s left join score sc on s.s_id=sc.s_id group by s.s_id
6、查询"王"姓老师的数量
select count(*) from teacher where t_name like '王%'
7、查询学过"张三"老师授课的同学的信息
select s.* from teacher t join course c join score sc join student s on t.t_id=c.t_id and c.c_id=sc.c_id and sc.s_id=s.s_id where t_name='张三'
8、查询学过编号为"01"并且也学过编号为"02"的课程的同学的信息
select s.* from score sc1 join score sc2 join student s on sc1.s_id=sc2.s_id and sc1.s_id=s.s_id where sc1.c_id='01' and sc2.c_id='02'
9、按平均成绩从高到低显示所有学生的所有课程的成绩以及平均成绩
思路:
查询所有学生的成绩,以student表为主表,和score进行外连接查询,之后根据学号分组统计每个学生的总分,让总分/总课程数得到平均分;在分组过程中对遍历到的数据进行判断,若c_id为01,则返回其分数值,作为语文这一列的值存在;若c_id为02,则返回其分数值,作为数学的成绩存在;若c_id为03,则返回其分数值,作为英语的成绩存在
考点:max()和case when 配合使用
select s.s_id,s_name,sum(s_score)/(select count(*) from course) av,
max(case when c_id='01' then s_score else 0 end)'语文',
max(case when c_id='02' then s_score else 0 end)'数学',
max(case when c_id='03' then s_score else 0 end)'英语'
from student s left join score sc on s.s_id=sc.s_id group by s.s_id order by av desc
10、查询各科成绩最高分、最低分和平均分:以如下形式显示:课程ID,课程name,最高分,最低分,平均分,及格率,中等率,优良率,优秀率 (及格为>=60,中等为:70-80,良好为:80-90,优秀为:>=90)
思路: 以课程表为主表,和成绩表进行外连接查询,统计得到每门课程的最高分,最低分,平均分,选修人数,使用sum()和case when配合使用,统计每个区间段的人数,该人数/总人数即为对应的百分比 select c.c_id,c_name,max(s_score),min(s_score),avg(s_score), sum(case when s_score>=60 and s_score<70 then 1 else 0 end)/count(s_id) '及格率', sum(case when s_score>=70 and s_score<80 then 1 else 0 end)/count(s_id) '中等率', sum(case when s_score>=80 and s_score<90 then 1 else 0 end)/count(s_id) '良好率', sum(case when s_score>=90 then 1 else 0 end)/count(s_id) '优秀率' from course c left join score sc on c.c_id=sc.c_id group by c.c_id
11、查询每门科目成绩都在60分及以上的学生id,姓名,成绩
注意:要求每门课程,包括没有选修的课程,成绩为各科的成绩 同学的思路:统计每个学生的最低分,筛选最低分大于等于60的学生? 错误的。 正确的思路: 使用表的自连接查询,将成绩表当成三份,一份作为课程1的成绩表使用,一份作为课程2的成绩表使用,一份作为课程3的成绩表使用,之后对三个表中的数据都进行大于等于60筛选,最后在三表中同时存在的id,即为要查找的学生学号,最后根据学号到学生表查询学生信息即可。---4表联查 select s.s_id,s_name,sc1.s_score '语文',sc2.s_score '数学',sc3.s_score '英语' from score sc1 join score sc2 join score sc3 join student s on sc1.s_id=sc2.s_id and sc2.s_id=sc3.s_id and sc1.s_id=s.s_id where sc1.c_id='01' and sc1.s_score>=60 and sc2.c_id='02' and sc2.s_score>=60 and sc3.c_id='03' and sc3.s_score>=60 或者: select s.s_id,s_name, max(case when c_id='01' then s_score else 0 end) chinese, max(case when c_id='02' then s_score else 0 end) math, max(case when c_id='03' then s_score else 0 end) english from student s join score sc on s.s_id=sc.s_id group by s.s_id having chinese>=60 and math>=60 and english>=60
12、查询至少有一门科目成绩在90分以上的学生id,姓名,成绩
select s.s_id,s_name, max(case when c_id='01' then s_score else 0 end)'语文', max(case when c_id='02' then s_score else 0 end)'数学', max(case when c_id='03' then s_score else 0 end)'英语' from student s join score sc on s.s_id=sc.s_id group by s.s_id having max(s_score)>90
13、查询所有科目成绩总和在240分及以上的学生id,姓名,和成绩总和,降序排列
select s.s_id,s_name,sum(s_score) sum from student s join score sc on s.s_id=sc.s_id group by sc.s_id having sum>=240 order by sum desc
14、查询每个科目成绩最高的学生的学生id,姓名,写在一个表里
将查询结果写入新表中: 语法: create table table_name as select...... 查询每门科目最高分对应的学生,同学的答案: select s_id,c_id,max(s_score) from score group by c_id -- 错误的 原因:group by 能查到的数据字段有:分组的字段,相关的聚合函数结果,其他无关数据查询不到 正确思路: 1. 先统计出每门课程的最高分 2. 再去成绩表中联查,查询某门课程最高分对应的学号是多少 3. 根据成绩表中查询出的学号,到学生表中查询对应的学生数据即可 create table new_score as select s.s_id,s_name,tmp.c_id,sc.s_score from student s join score sc join (select c_id,max(s_score) max from score group by c_id)tmp on tmp.c_id=sc.c_id and tmp.max=sc.s_score and sc.s_id=s.s_id
15、查询每个科目成绩最低的学生的学生id,姓名
16、查询目前没有安排课程的老师姓名
思路:统计每个老师的授课数,筛选授课数为0的数据 select t_name from teacher t left join course c on t.t_id=c.t_id group by t.t_id having count(c_id)=0
17、将之前老师表的英文名字转化为大写
函数: upper(col) -- 将该列的值转换为大写 lower(col) -- 将该列的值转换为小写 update teacher set t_name=upper(t_name)
18、查询学生表中重复的姓名
思路:统计每个姓名的次数,筛选出现次数大于1的 select s_name from student group by s_name having count(s_id)>1
19、统计各科成绩各分数段人数:课程编号,课程名称,[100-85],[85-70],[70-60],[0-60]及所占百分比
concat(e1,e2) -- 将两部分拼接起来 select c.c_id,c_name, sum(case when s_score>=85 and s_score<=100 then 1 else 0 end)'[100-85]', sum(case when s_score>=70 and s_score<85 then 1 else 0 end)'[85-70]', sum(case when s_score>=60 and s_score<70 then 1 else 0 end)'[70-60]', sum(case when s_score<60 then 1 else 0 end)'[0-60]', concat(round(sum(case when s_score>=85 and s_score<=100 then 1 else 0 end)/count(s_id)*100,0),'%') '[100-85]百分比', concat(round(sum(case when s_score>=70 and s_score<85 then 1 else 0 end)/count(s_id)*100,0),'%') '[85-70]百分比', concat(round(sum(case when s_score>=60 and s_score<70 then 1 else 0 end)/count(s_id)*100,0),'%') '[70-60]百分比', concat(round(sum(case when s_score<60 then 1 else 0 end)/count(s_id)*100,0),'%') '[0-60]百分比' from course c left join score sc on c.c_id=sc.c_id group by c.c_id视图 - View
-
什么是视图
视图是一张虚拟表,视图中并不保存数据,其数据均来源于真实表中。
eg:有4张表,有一个业务场景需要从4表中查询出一些数据,展示到页面上,页面上显示的数据来源于4张表(4表联查),sql为:select…from…,查询的结果分布在4张表中。若好几个业务场景中需要查询以上结果集,在页面展示,若将以上的sql重复写多次,可以实现效果,但是代码冗余,不建议这样做。如何提高sql的复用率?通过视图来解决这个问题。
解决办法:将以上的sql封装起来,给这个sql起一个名称,后续若要再次执行以上sql,直接通过名称调用该sql即可,这里就是通过视图将该sql封装起来了。
视图其实是将一个sql封装起来,后续若要调用该sql,直接通过视图名调用即可。
-
视图操作
-
创建视图
create view view_name as select…- 当查询的结果集中字段名不重复,可以采用第一种语法
create view view_name(col1,col2,col3…) as select… - 当查询的结果集中字段名重复/冲突,此时只能采用第二种语法
场景:从2表联查,查询出想要的一些字段(来源于2表中),作为视图展示
eg: 从emp和dept表查询出员工信息:显示:员工id,员工姓名,部门id,部门名称 - 当查询的结果集中字段名不重复,可以采用第一种语法
-
查询视图
-
查询视图结构
desc table_name;
desc view_name; -
查询视图数据 — 调用视图
select…from table_name
select…from view_name -
查看所有视图
show tables; -
删除视图
删除表: drop table table_name
删除视图: drop view view_name;
因为视图是虚拟表,所以对视图的操作和对表的操作一致。
-
-
视图注意点:
- 视图是虚拟表,并不保存数据,实质是封装了查询的sql,所以视图并不会提高查询效率
- 因为视图中并不保存数据,数据来源于真实表中,所以若真实表中的数据发生改变,视图中的数据也会随之改变。
- 视图是用于封装查询sql的,是用于保存结果集,方便查询的,而不是用于执行update操作(增删改)的,所以逻辑上不要也不应该对视图执行update操作;但是物理角度,数据库确实允许对视图执行update操作,不一定成功(部分成功,部分失败)
- 视图是源于单表
- 对视图执行update(增删改),最终原表中会发生相应的改变
- 视图来源于多表
- 执行修改操作
- 若修改的数据是某一张表的数据,则成功
- 若同时修改多个表中的数据,则失败
- 增删,均失败
- 执行修改操作
- 视图是源于单表
-
事务
事务是数据库中执行操作的最小执行单元,要么全都成功,要么全都失败。
-
事务操作
开启事务
执行操作。。。。。
以上操作都成功,事务提交/以上操作但凡一步出错,事务回滚对应的sql:
begin – 开启事务
commit --提交事务
rollback – 回滚事务注意:对数据库执行单个增删改操作,默认都会开启事务,执行成功,默认提交事务,执行失败,默认回滚事务。查询操作默认不涉及事务
-
mysql中的事务管理
mysql中事务管理的自动提交默认开启
1.如何查看数据库自动提交是否开启?
– show variables like ‘autocommit’
属性:autocommit 属性值: on-开启 off–关闭
2.如何关闭数据库的自动提交
set autocommit=off/on
–验证增删改均涉及到事务了
1. 将数据库的自动提交关闭
2. 执行update操作验证
-
事务的特性: – 面试高频
原子性:事务是最小的执行单元,不可再分,其中的操作要么全都成功,要么全都失败
一致性:事务会从一个一致性状态切换到另一个一致性状态
持久性:事务一旦提交,执行的操作会持久化到数据库中
隔离性:多事务并发执行,彼此之间相互隔离,互不影响。 -
事务的隔离级别 – 面试非常高频 – 必问
读未提交 – read uncommitted
读已提交 – read committed
可重复读 – repeatable read
可串行化 – serializable -
读未提交
事务可以读取到其他事务未提交/未回滚前的数据,可能会产生脏读,如何解决脏读,提升隔离级别为读已提交
-
读已提交
事务只能读取到其他事务提交/回滚后的数据,这种隔离级别解决了脏读问题,但是可能会产生的新的问题 – 不可重复读
不可重复读产生的原因:在事务执行期间,其他事务对操作的行数据进行了修改,导致同一个事务前后两次读取相同的数据,结果不一致为了解决不可重复读,产生了新的隔离级别:可重复读
-
可重复读
可重复读:当事务访问行数据时,会给访问到的所有行数据加锁,保证在事务执行期间,其他事务无法修改这些数据,从而避免了不可重复读问题。
解决了不可重复读问题,但是可能会产生新的问题 - 幻读
幻读产生的原因:是因为在事务A执行期间,其他事务对事务A操作的数据外的其他数据执行了增删操作,导致数据总量不一致,在事务A中就好像产生了幻觉
为了解决幻读,产生了新的隔离级别 – 可串行化
-
可串行化
当事务访问数据时,立即给表加锁,从而保证在事务执行期间,其他事务无法对表执行增删操作。从而避免幻读
-
隔离级别的隔离性与并发行
隔离级别按照 读未提交–>读已提交–>可重复读–>可串行化这个顺序,隔离级别越来越高的,但是并发执行效率时越来越低的。
-
常见关系型数据库默认的隔离级别
sql server和oracle默认的隔离级别是读已提交·
mysql默认的隔离级别为:可重复读 -
mysql的隔离级别为可重复读,是否会产生幻读?
不会
mysql中通过快照读的方式解决了幻读 -
各种隔离级别产生的和解决的问题
-
分类
- 按照锁的粒度分:
行锁
表锁 - 按照锁的类型分:
排他锁/独占锁: update锁
特点:加了排他锁,针对这行/表数据,不再允许其他任何排他锁和共享锁添加上来。
共享锁: share锁
特点:加了共享锁,允许在共享锁基础上对目标数据加共享锁,但是不允许加排他锁
总结:排他锁和共享锁均可以作用在行数据上,也可以作用在表数据上,各自的特点为:
排他锁不允许再加其他任何锁
共享锁允许继续加共享锁,但是不允许加排他锁 - 按照锁的粒度分:
-
排他锁共享锁的应用
通常:若执行写操作,会选择给目标数据加排他锁,以保证数据安全
若执行读操作,如果需要加锁,会选择给目标数据加共享锁
如何在select操作时给目标数据加共享锁?
- select…for share --查询时加共享锁
- select…for update --查询时给数据加排他锁数据库中共享锁和排他锁的应用:
1. 隔离级别为可重复读,给行数据加的是排他锁
2. 隔离级别为可串行化,给表加的是共享锁
注意:mysql中执行CRUD操作时的加锁问题:
mysql的隔离级别默认为可重复读,所以mysql中的事务会默认给操作的行数据加排他锁
mysql中执行增删改会默认开启事务管理,会默认对操作的数据加行排他锁,
执行select操作默认不加锁
-
索引的作用
索引是作用于列上的,用于对该列的数据进行排序,形成一个目录,从而提高其查询效率。
-
索引的底层机制
索引的底层是B+Tree数据结构
B+Tree是基于BTree实现的,所以先了解BTree数据结构 -
BTree
BTree中是以数据块的形式来保存数据的,每个数据块中保存元素的数量=度-1
BTree会对存入的元素进行排序,当数据块中保存的元素数量达到度的值时,此时会进行分裂提取,将数据块最中间的元素提取到上一级数据块,将中间元素的左右两侧分裂为2个数据块。
-
BTree的优点
查询效率非常高,每次比较可以排除很多数据
-
B+Tree
和BTree一样,都是以数据块的形式保存数据,且每个数据块保存的元素数量=度-1
当数据块中的元素数量达到度的值时,均会进行分裂提取和BTree的区别:
1. B+Tree中若从叶子数据块中进行分裂提取,提取后,元素会依然存在于叶子数据块中;若从非叶子数据块中进行分裂提取,提取后,元素不再在原来的数据块中
– 这样做的原因:B+Tree中保存的所有数据都会出现在叶子数据块中,即查询元素时,该元素必然存在于叶子数据块中,而非叶子数据块构成的树仅仅作为目录使用。
2. B+Tree中叶子数据块之间通过单向链表进行连接,目的是为了提高区域范围内数据的查询效率。eg:查询6-20之间的数据,只需要从B+Tree找到6,之后顺序链表继续查找元素即可。
索引失效是指:因为一些不当操作,导致进行全表扫描,而不适用索引,这种情况我们叫做索引失效。
使用索引时sql语句要避免的情况:
1.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描
2.应尽量避免在 where 子句中使用!=操作符,否则将引擎放弃使用索引而进行全表扫描
3.应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描
4.not in 也要慎用,否则会导致全表扫描,in并不会导致索引失效
5.尽量避免在where子句中对字段使用like左侧模糊查询,会导致全表扫描
6.应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描
eg: select…from user where age+4>12
7.应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描
eg:select…from …where avg(score)>…
使用索引注意事项:
索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。
-
索引提高查询效率的原因
-
B+Tree可以实现查询时排除大量数据,从而减少比较元素的次数
-
索引可以大大降低查询过程中对磁盘的IO操作次数,保证对磁盘的IO操作一直是3次
若对表进行顺序查找,每次读取一次数据均是进行一次磁盘IO操作,时间级别是ms级别,若查找的元素在偏后位置,则耗时非常长,查询效率非常低下;使用了索引后,可以大大降低对磁盘的IO操作次数,原因是:索引的高度一定是3,每次查找时,需要读取一个数据块,读取数据块是对磁盘的IO操作,则最终对B+Tree进行了3次磁盘IO,每次读取到数据块后,会将读取到的数据块缓存到内存中,之后从内存中使用二分查找算法查找目标元素所处的区间位置,这里是对内存进行IO操作,时间级别是ns级别。
-
-
索引使用B+Tree的特点
索引底层数据结构是B+Tree,但是在叶子数据块中保存的元素是键值对。
索引类型不同,键值对中的key和value保存的数据是不同的 -
索引的分类
- 聚集索引(主索引):给主键添加的索引就叫做聚集索引
- 非聚集索引(辅助索引):给非主键字段添加的索引据叫做非聚集索引
-
聚集索引
mysql从5.5版本开始,数据库存储引擎为Innodb,Innodb存储引擎有以下2个特点:
1. Innodb存储引擎支持事务和行锁
2. Innodb自动会给表的主键添加聚集索引,若表中没有主键,则Innodb存储引擎会自动的创建一个主键,类型为long,长度为6,且给该字段添加聚集索引;除此之外,Innodb存储引擎会默认给添加了unique约束,外键约束的字段添加索引。unique约束:给字段添加该约束后,要求字段的值必须唯一存在
外键约束(foreign key):某个表中的非主键字段的值来自于另外一张表中的主键字段的值;特点:给某字段添加了外键约束后,若要删除另外一张表中被引用的主键字段数据,若该数据正在被作为外键值使用,则删除失败 – 了解即可 -
索引的叶子数据块中保存的key-value
聚集索引的叶子数据块中保存的key为主键(id),value为对应的行数据
非聚集索引的叶子数据块中保存的key为添加了索引的字段值,value为对应的主键值-id
-
索引操作
1.创建索引
create index index_name on table_name(col)
2.查询索引
show index from table_name
3.删除索引
drop index index_name on table_name -
练习
1.选择一张表,要求有主键,以及有一个字段有unique约束, 2.之后给某个非主键字段添加索引 3.添加完成,查询表中的所有索引--观察是否有3个索引 如何在表外给某字段添加unique约束 alter table user modify name varchar(20) unique; -
索引的适用场景
- 表中的数据量大时,应该使用索引.表中数据量不大,不要使用索引,因为建立索引也是需要时间的.
- 通常会给作为查询条件的字段添加索引
- 当某字段的值会被频繁修改时,不要给该字段添加索引,因为每次修改都会改变元素的排序,从而导致索引重构,耗费时间
- 在一个表中,索引并不是越多越好,通常情况下,一个表中的索引不要超过6个
- 索引失效的场景:
索引失效是指:因为一些不当操作,导致进行全表扫描,而不使用索引,这种情况我们叫做索引失效。
使用索引时sql语句要避免的情况:
1.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描
2.应尽量避免在 where 子句中使用!=操作符,否则将引擎放弃使用索引而进行全表扫描
3.应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描
4.not in 也要慎用,否则会导致全表扫描,in并不会导致索引失效
5.尽量避免在where子句中对字段使用like左侧模糊查询(like ‘_/%’),会导致全表扫描
6.应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描
eg: select…from user where age+4>12
7.应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描
eg:select…from …where round(score)>…
使用索引注意事项:
索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。
SQL优化也就是SQL调优
如何调优?
1. 找到慢sql日志,确定哪些sql执行慢,针对这些sql进行优化
2. 具体调优:
1. 查看sql语句是否使用了*,若使用*,修改为具体字段
2. 检查sql中是否使用了不必要的嵌套查询,若有,则尽可能地转换为多表联查
(连接查询效率高于嵌套查询)
3. 检查sql中的查询条件部分是否需要索引以及是否已经添加了索引,若需要索引,但没有添加索引,则添加索引。
进行SQL优化的第一步,是先查找哪些查询sql执行效率低。
慢sql日志功能:
-在mysql会有一个日志文件用于记录执行时间超过指定时间的sql,这个日志文件就叫做慢sql日志。
- 该功能在mysql中默认不开启,若想使用该功能,需要开启
慢sql日志文件在数据库安装路径中的data目录下(前提:打开慢sql日志功能)
- 操作慢sql日志的sql:
- 查看慢sql日志是否打开:show variables like ‘slow_query_log’;
- 开启慢sql日志功能:set slow_query_log=on
执行时间超过10s时,默认视为慢sql,会将该sql记录到日志文件中。 - 查询慢sql日志设置的时间:show global variables like ‘long_query_time’
- 如果需要,可以修改设置时间(阈值时间): set global long_query_time=2
- 查看日志文件: show variables like ‘slow_query_log_file’
注意点:对慢sql日志功能设置完成,需要重启数据库服务器,才能生效
数据库的五大约束1. 主键约束 -- primary key 2. 唯一性约束 -- unique 3. 非空约束 -- not null 4. 检查约束 -- check 5. 外键约束 -- foreign key 五大约束都是作用与列上的,用于约束列的值必须符合什么样的条件
-
主键约束
字段添加主键约束后,特点为:非空且唯一的
主键约束包含了非空约束和唯一性约束约束的添加:
1. 创建表时添加主键约束
create table user(id int primary key,
name varchar(20));
2. 创建表后给字段添加主键约束
alter table user modify id int primary key; -
唯一性约束 – unique
给字段添加该约束后,要求该字段的值必须唯一存在
添加约束时机:
1. 创建表时添加约束
create table user(id int primary key,
name varchar(20) unique,
tel varchar(11));
2. 创建表后添加约束
alter table user modify tel varchar(11) unique; -
非空约束 - not null
给字段添加该约束后,要求字段的值必须非空
添加时机:
- 创建表时添加
create table user(id int primary key,
name varchar(20) unique not null,
tel varchar(11)); - 创建表后添加
alter table user modify tel varchar(20) not null;
- 创建表时添加
-
检查约束 - check
给字段添加该约束后,要求该字段的数据值必须满足给出的条件,若不满足,则添加失败
语法: check(age between 12 and 15)添加时机: 1. 创建表时添加 create table user(id int primary key, name varchar(20) not null unique, age int check(age between 10 and 20)) 2. 创建表后添加 alter table user modify age int check(age>=20) 关键字: constraint 约束 注意点:使用的数据库版本不同,可能会导致检查约束不生效 - mariadb10 -- 生效的 - mysql 5.5 mysql8.x -- 检查约束失效 -
外键约束 - foreign key
使用外键约束的场景:
当表A中的非主键字段的值引用自另一张表B中的主键字段,此时就可以给这个表A中的这个非主键字段添加外键约束。
添加该约束后的特点:
若表B中的主键字段值正在被表A作为外键使用,则不能删除表B中的数据,必须等待表A中不再引用主键值后,才能删除表B的数据注意点:目前市面上基本禁用外键约束,不适用物理外键,都使用逻辑外键
添加外键的sql:
- 创建表时添加
create table emp(id int primary key,
name varchar(20),
dept_id int,
foreign key(dept_id) references dept(id))
练习:
1. 创建员工表emp2,给部门编号添加外键约束,
2. 删除部门表中的数据,查看是否删除成功 - 创建表后添加
alter table emp add constraint 约束名(emp_fk) foreign key(dept_id) references dept(id)
- 创建表时添加
实现的核心思想:
1. 构造方法私有化
2. 提供一个共有的方法向外部始终提供同一个实例即可。
-
分类
1. 懒汉式单例 -- 笔试题高频出现 懒汉式:懒加载机制--什么时候第一次用,什么时候创建实例 2. 饿汉式单例 -
懒汉式单例
1. 提供私有的构造方法 2. 在成员变量位置声明static的当前类型引用,不初始化 3. 提供一个共有的static的方法向外部提供实例 4. 为了保证多线程并发执行时获取到的依然是单例,需要让方法成为同步资源 public class LazySingleton { //构造方法私有化 private LazySingleton(){} //声明当前类型引用 private static LazySingleton singleton; //提供static的共有的方法向外部提供实例 public static synchronized LazySingleton getInstance(){ if (singleton==null){ singleton = new LazySingleton(); } return singleton; } -
饿汉式单例
1. 构造方法私有化 2. 在成员变量位置声明当前类型的static引用并进行实例化(饿汉式) 3. 提供公有的static的方法用于向外部提供实例 public class HungrySingleton { //构造方法私有化 private HungrySingleton(){} //声明static当前类型引用且同时实例化,static变量是在类加载时期完成初始化的 private static HungrySingleton singleton = new HungrySingleton(); //提供共有的static方法向外部提供实例 public static HungrySingleton getInstance(){ return singleton; } }



