按照以日为维度查询一周中每一天的统计数 使用DATE_FORMAT(r.createDate,‘%Y-%m-%d’)最后要group by DATE_FORMAT(r.createDate,‘%Y-%m-%d’)
按照以月为维度查询半年中每月的统计数 使用DATE_FORMAT(r.createDate,‘%Y-%m’)最后要group by DATE_FORMAT(r.createDate,‘%Y-%m’)
mysql中使用date_add()函数
了解到MySql的日期函数date_sub(now(), interval 7 day)
比如now() 得到当前时间是 2022-01-15 22:36:43
那么date_sub(now(), interval 7 day) 得到的时间就是 2022-01-08 22:36:43
current_date 获取当前时间,精确到日
(重点:@PostMapper)在restful风格里面 传参里面的参数必须序列化,要不然只会传入其格式例如:json/application
@PostMapper @RequestBody @Valid DynamicTrendAnalysisParam queryParam
查询时不能全部查出,试试分俩个sql写出来
数据概括:(查询慢的sql)select 查询 关联字段放到where 比如 u.tenanatId=s.tenantId 还有查询时间范围拿到where里面
类型分析: 按照type进行分析就要group by type 关键字on(里面只要关联字段)
热度分析:SaleManRankReportServiceImpl 296行
Set typeEnumSet = new HashSet<>();
typeEnumSet.add(ClewTypeEnum.STYLE);
typeEnumSet.add(ClewTypeEnum.PHOTO);
typeEnumSet.add(ClewTypeEnum.RECOM_ATTIRE);
typeEnumSet.add(ClewTypeEnum.IMG_TEXT);
for (ClewTypeEnum typeEnum:typeEnumSet){
if(map.containsKey(typeEnum)){
DynamicHeatAnalysisVo dynamicHeatAnalysisVo= map.get(typeEnum);
voList.add(dynamicHeatAnalysisVo) ;
}else{
DynamicHeatAnalysisVo dynamicHeatAnalysisVo= new DynamicHeatAnalysisVo(typeEnum,null);
voList.add(dynamicHeatAnalysisVo) ;
}
}
1462行CustomerMapper.xml
关于在controller中传参把wrapper作为参数传递
https://blog.csdn.net/weixin_42260782/article/details/122232629?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165085451516781483756079%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=165085451516781483756079&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_ecpm_v1~rank_v31_ecpm-1-122232629.142v9control,157v4control&utm_term=mapper.xml%E4%B8%ADew%E6%98%AF%E5%95%A5%E6%84%8F%E6%80%9D&spm=1018.2226.3001.4187
List 转 Map<String, T>
List 转 Map<String, List>
List 转 Map<String, Count>
List<Map<String, Object>> 转 Map<String, List<Map<String, Object>>>
List 转 Map<String, List>
使用groupingBy根据gender属性分组
https://blog.csdn.net/winterking3/article/details/116457573?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165088493416782350981412%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165088493416782350981412&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogfirst_rank_ecpm_v1~rank_v31_ecpm-4-116457573.nonecase&utm_term=stream%E6%B5%81%E5%A4%84%E7%90%86list%E8%BD%ACmap&spm=1018.2226.3001.4450
Jenkins+Docker的操作
https://blog.csdn.net/hlkt521/article/details/107188180?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165103454016781432988457%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=165103454016781432988457&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_positive~default-1-107188180.142v9control,157v4control&utm_term=jenkins&spm=1018.2226.3001.4187
在进行模糊查询时(查询货号或者商品名称)
不加括号: AND st.styleName LIKE CONCAT( ‘%’, ‘ww’, ‘%’ ) OR ( st.styleId LIKE CONCAT( ‘%’, ‘ww’, ‘%’ ))
加了括号:AND (st.styleName LIKE CONCAT( ‘%’, ‘ww’, ‘%’ ) OR ( st.styleId LIKE CONCAT( ‘%’, ‘ww’, ‘%’ )))
区别: 加入括号只能查货号 和 产品名称
不加括号:只能查询商品名称 或者货号 而且不加括号数据比加了括号的数据要多
不加括号:or前面条件失效,后面条件生效
https://blog.csdn.net/liyue071714118/article/details/108656396?ops_request_misc=&request_id=&biz_id=102&utm_term=mysql%E4%B8%ADand%E5%92%8Cor%E7%9A%84%E5%8C%BA%E5%88%AB&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-2-108656396.nonecase&spm=1018.2226.3001.4187
判断一个数据是否为0 if(!xxx){}
CPU在多个线程间切换,可能导致某些重要的指令不能完整执行,出现数据的问题。
出现线程安全问题的三个条件:
/**
* 银行转账的案例
*/
public class BankDemo {
//模拟100个银行账户
private int[] accounts = new int[100];
{
//初始化账户
for (int i = 0; i < accounts.length; i++) {
accounts[i] = 10000;
}
}
/**
* 模拟转账
*/
public void transfer(int from,int to,int money){
if(accounts[from] < money){
throw new RuntimeException("余额不足");
}
accounts[from] -= money;
System.out.printf("从%d转出%d%n",from,money);
accounts[to] += money;
System.out.printf("向%d转入%d%n",to,money);
System.out.println("银行总账是:" + getTotal());
}
/**
* 计算总余额
* @return
*/
public int getTotal(){
int sum = 0;
for (int i = 0; i < accounts.length; i++) {
sum += accounts[i];
}
return sum;
}
public static void main(String[] args) {
BankDemo bank = new BankDemo();
Random random = new Random();
//模拟多次转账过程
for (int i = 0; i < 50; i++) {
new Thread(() -> {
int from = random.nextInt(100);
int to = random.nextInt(100);
int money = random.nextInt(2000);
bank.transfer(from,to,money);
}).start();
}
}
}
解决方法:给程序上锁,让当前线程完整执行一段指令,执行完释放锁,其它线程再执行
几种不同上锁方法:
给方法添加synchronized关键字
作用是给整个方法上锁
过程:
当前线程调用方法后,方法上锁,其它线程无法执行,调用结束后,释放锁。
/**
* 模拟转账
*/
public synchronized void transfer(int from,int to,int money){
if(accounts[from] < money){
throw new RuntimeException("余额不足");
}
accounts[from] -= money;
System.out.printf("从%d转出%d%n",from,money);
accounts[to] += money;
System.out.printf("向%d转入%d%n",to,money);
System.out.println("银行总账是:" + getTotal());
}
锁对象:
粒度比同步方法小,粒度越小越灵活,性能更高
给一段代码上锁
synchronized(锁对象){
代码
}
锁对象,可以对当前线程进行控制,如:wait等待、notify通知;
任何对象都可以作为锁,对象不能是局部变量
//同步代码块
synchronized (lock) {
accounts[from] -= money;
System.out.printf("从%d转出%d%n", from, money);
accounts[to] += money;
System.out.printf("向%d转入%d%n",to,money);
System.out.println("银行总账是:" + getTotal());
}
synchronized的基本的原理:
一旦代码被synchronized包含,JVM会启动监视器(monitor)对这段指令进行监控
线程执行该段代码时,monitor会判断锁对象是否有其它线程持有,如果其它线程持有,当前线程就无法执行,等待锁释放
如果锁没有其它线程持有,当前线程就持有锁,执行代码
底层汇编实现:
monitorenter
....
monitorexit
在java.concurrent并发包中的
Lock接口
基本方法:
常见实现类
使用方法:
//成员变量
Lock lock = new ReentrantLock();
//方法内部上锁
lock.lock();
try{
代码...
}finally{
//释放锁
lock.unlock();
}
三种锁对比:
粒度
同步代码块/同步锁 < 同步方法
编程简便
同步方法 > 同步代码块 > 同步锁
性能
同步锁 > 同步代码块 > 同步方法
功能性/灵活性
同步锁(有更多方法,可以加条件) > 同步代码块 (可以加条件) > 同步方法
- 串行
多个指令依次执行
- 并发
每个线程单独执行一段指令,一个cpu在线程间切换(并不是同时执行)
- 并行
多个CPU内核同时执行多个线程,线程是同时执行的
- 进程是程序执行相关资源(CPU、内存、磁盘等)分配的最小单元
进程之间是相互独立的,有自己的内存空间
- 线程是CPU资源分配的最小单元
进程包含一个或多个线程
线程需要的资源更少,可以看做是一种轻量级的进程
线程会共享进程中的内存,线程也有独立的空间(栈、程序计数器)
线程相互通信更加方便
1. 继承Thread类
2. 实现Runnable接口
3. 实现Callable接口
4. 使用线程池
作用:回收利用线程资源
线程是一种宝贵的系统资源,执行完任务后会死亡,如果有大量任务需要处理,需要 频繁的创建和销毁线程,造成系统性能降低。
线程池会保存一定量的线程,线程执行完任务后,会回到线程池中,等待下一个任 务,节省系统资源,提升性能
#### 线程池的使用
顶层接口:Executor
添加线程池管理方法,如:shutdown()、shutdownNow()
用于创建线程池的工具类
主要的方法
方法名 | 说明 |
---|---|
newCachedThreadPool() | 创建长度不限的线程池 |
newFixedThreadPool(int ) | 创建固定长度的线程池 |
newSingleThreadExecutor() | 创建单一个数的线程池 |
newScheduledThreadPool(int) | 创建可以调度的线程池 |
建议自己百度白嫖+自己理解!!!
线程几种状态:
- 新建 NEW
- 准备/就绪 START
- 运行 RUNNING
- 阻塞 BLOCKING
- 死亡 DEAD
常用方法
方法 | 介绍 |
---|---|
start() | 启动 |
stop() | 停止(禁用,可能导致线程死锁等问题),停止线程可以让run执行结束 |
String getName() | 获得线程的名字 |
setName(String) | 设置线程名字 |
sleep(long) | 进入睡眠,毫秒 |
setPriority(int) | 设置线程的优先级(1~10从低到高)越高抢CPU几率更高 |
setDaemon(boolean) | 设置为后台线程 true ,后台线程是为其它线程服务的,如果没有其它线程存在,就自动死亡;使用案例:GC就是一种后台线程 |
join() | 线程的加入(合并)让其它线程先执行完,再执行自己的指令 |
2.1 synchronized 与 volatile 的区别?
volatile是一个类型修饰符(type specifier);
volatile,它能够使变量在值发生改变时能尽快地让其他线程知道;
关键字volatile是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,并且只能修改变量,而synchronized可以修饰方法,以及代码块;
多线程访问volatile不会发生阻塞,而synchronized会出现阻塞;
volatile能保证数据的可见性,但不能保证原子性;而synchronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公共内存中的数据做同步;
关键字volatile解决的下变量在多线程之间的可见性;而synchronized解决的是多线程之间资源同步问题。
2.2 什么是乐观锁,悲观锁?
https://blog.csdn.net/firstcode666/article/details/122789487?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165594641016780357273021%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=165594641016780357273021&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduend~default-1-122789487-null-null.142v20control,157v15new_3&utm_term=%E4%BB%80%E4%B9%88%E6%98%AF%E4%B9%90%E8%A7%82%E9%94%81%EF%BC%8C%E6%82%B2%E8%A7%82%E9%94%81&spm=1018.2226.3001.4187
2.3 锁有哪几种(锁的分类)?
https://blog.csdn.net/m0_60092917/article/details/118880821?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165594644416781667853241%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=165594644416781667853241&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_ecpm_v1~rank_v31_ecpm-2-118880821-null-null.142v20control,157v15new_3&utm_term=+%E9%94%81%E6%9C%89%E5%93%AA%E5%87%A0%E7%A7%8D%EF%BC%88%E9%94%81%E7%9A%84%E5%88%86%E7%B1%BB%EF%BC%89&spm=1018.2226.3001.4187
ypora-user-images\1655945696558.png)]](https://img-blog.csdnimg.cn/ccb0b087060a4faab383505a18d33d3a.png)
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0EqFlGGo-1656029061361)(C:\Users\Dinghao\AppData\Roaming!在这里插入图片描述
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 判断容量 扩容
elementData[size++] = e; //存数据到数组中
return true;
}
private void ensureCapacityInternal(int minCapacity) {
//保证新集合的容量不能低于10
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//扩容
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//判断当前容量是否超过数组的长度
if (minCapacity - elementData.length > 0)
grow(minCapacity); //扩容
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); //新容量是原来容量的1.5倍
if (newCapacity - minCapacity < 0) //保证新容量不会低于传入的参数
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0) //保证新容量不能超过上限 Integer.MAX_VALUE - 8
newCapacity = hugeCapacity(minCapacity);
//使用新容量创建新数组,将原数组的数据复制过去,将新数组赋值给原数组的引用
elementData = Arrays.copyOf(elementData, newCapacity);
}
大伙建议自己百度
自己的理解(自己的口述)
Java中的一种高级技术,能够在程序运行时能够解析出任意类和对象的属性和方法,可以动态调用类和对象的所有属性和方法。
可以用于开发非常灵活和通用的代码
应用场景:开发各种框架,如:Spring、MyBatis等
优点 : 可以让咱们的代码更加灵活、为各种框架提供开箱即用的功能提供了便利
缺点 :让我们在运行时有了分析操作类的能力,这同样也增加了安全问题。比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说实际是影响不大的。
像咱们平时大部分时候都是在写业务代码,很少会接触到直接使用反射机制的场景。
但是,这并不代表反射没有用。相反,正是因为反射,你才能这么轻松地使用各种框架。像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。
这些框架中也大量使用了动态代理,而动态代理的实现也依赖反射。
主要做数据缓存
https://blog.csdn.net/qq_42992919/article/details/96129147?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165594659716781667889047%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=165594659716781667889047&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_ecpm_v1~rank_v31_ecpm-2-96129147-null-null.142v20control,157v15new_3&utm_term=redis%E7%9A%84%E4%BC%98%E7%BC%BA%E7%82%B9%EF%BC%9F%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%8D%E7%94%A8MongoDB%EF%BC%9F&spm=1018.2226.3001.4187
Redis的数据以key-value方式存储。
数据类型有:
string 字符串(简单的key-value数据,适合保存单个数据,如:用户数量、库存数、商品数)
hash 哈希(适合保存复杂类型数据,如:用户对象)
list 列表(链表结构,适合保存有序的、可重复的数据,如:商品列表、评论列表)
set 无序集合(适合保存无序的,不可重复的数据)
zset 有序集合(适合保存有序的,不可重复的数据)
1)缓存击穿
高并发的情况下,短时间内缓存会被穿过,请求直接打到数据库上,可能导致数据库压力过大。
解决方案:对代码上锁(双重检查锁)
2)缓存穿透
高并发的情况下,如果查询不存在的数据,因为缓存和数据库都不存在,请求都会打到数据库上,可能导致系统崩溃。
解决方案:
1) 保存不存在的数据到缓存中,设置一定过期时间
2) 布隆过滤器(直接过滤掉不存在数据的请求) 不能准确判断是否存在数据,能准确判断数据不存在
3)缓存雪崩
高并发的情况下,缓存服务器重启或热点数据同时过期,全部访问数据库,导致数据库宕机
解决方案:
1)配置缓存集群
2)尽量给热点数据设置不一样的过期时间,相对均匀
解决代码
public Goods getGoodsById(Long id){
ValueOperations<String, Object> ops = redisTemplate.opsForValue();
Object value = ops.get(TYPE + id);
//外层先读缓存,缓存如果有,就不执行同步块
if(value == null) {
synchronized (this) {
//1) 以id为键查询Redis缓存,如果能查到就返回数据,结束
value = ops.get(TYPE + id);
//2)如果查不到,就查询数据库
if (value == null) {
System.out.println("缓存不存在,查询数据库");
// 数据库查到,缓存到Redis,返回
Goods goods = this.getById(id);
if (goods != null) {
System.out.println("数据库存在,保存到缓存");
ops.set(TYPE + id, goods);
} else {
System.out.println("数据库不存在,返回null");
//保存空数据到缓存中,设置过期时间
ops.set(TYPE + id,new Goods(),30, TimeUnit.SECONDS);
}
return goods;
} else {
System.out.println("缓存存在,返回" + value);
//如果能查到就返回数据,结束
return (Goods) value;
}
}
}
System.out.println("缓存存在,返回" + value);
return (Goods) value;
}
做一个热点模块比如泡泡教育平台你那个广告模块(你进入网站就广告图片开始时,就把登录时的token携带进入微服务,可以解决这个问题啦,这个广告模块是实时运行的,每次刷新或者运行功能,就会刷新token的过期时间,不断刷新就可以一直携带有效的token)
https://blog.csdn.net/wujialv/article/details/109325092?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165594721916782246457908%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=165594721916782246457908&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_ecpm_v1~rank_v31_ecpm-1-109325092-null-null.142v20control,157v15new_3&utm_term=redis%E9%99%A4%E4%BA%86%E5%81%9A%E7%BC%93%E5%AD%98%E8%BF%98%E8%83%BD%E5%B9%B2%E5%98%9B%E3%80%81&spm=1018.2226.3001.4187
https://blog.csdn.net/qqqwwweeerasd/article/details/124464919?ops_request_misc=&request_id=&biz_id=102&utm_term=%E4%BA%86%E8%A7%A3%E8%BF%87redis%E6%BA%90%E7%A0%81%E5%98%9B&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-1-124464919.142v20control,157v15new_3&spm=1018.2226.3001.4187
6.1什么是mq?(干嘛用的)
消息队列(Message Queue)简称MQ,简单来说就是一种消息的容器
作用是采用FIFO(先入先出)的方式实现程序之间(服务、进程、线程)的消息通信。
6.2 mq 怎么使用(如何在项目中配置)
自己百度
6.3 mq它的作用是干啥(保持缓存与数据库数据一致)?
我们还是用了 RabbitMQ 来实现了 ES 和 Mysql 数据的同步。其中实现的 步骤为我们生成了一个 topic 主题类型的交换机,然后分别绑定了两条队列在该交换机上,两条队列分别设置对应 的 key,一个队列用于增改,一个队列用于删除。当我们的后台对 Mysql 的数据进行了增改或删除时就会向对应 key 的队列中发送消息,消息内容就是操作的数据,然后我们的 Es 服务方会对我们的 MQ 的两条队列进行实时的 监听。当在对应的队列中收到消息,就会执行操作,对我们的 es 执行增改或查询的操作。这样我们就实现了 ES 和 Mysql 的数据同步。
6.4 mq 有几种消息队列?(5种)1:1((能者多劳),1 : n,工作,路由,主题
RabbitMQ提供了多种消息模型,官网上第6种是RPC不属于常规的消息队列。
属于消息模型的是前5种:
1. 简单的一对一模型
2. 工作队列模型 ,一个生产者将消息分发给多个消费者
3. 发布/订阅模型 ,生产者发布消息,多个消费者同时收取
4. 路由模型 ,生产者通过关键字发送消息给特定消费者
5. 主题模型 ,路由模式基础上,在关键字里加入了通配符
6.5 mq 消息丢失怎么处理?
1. 生产者到交换机
2. 交换机到队列
3. 队列到消费者
解决方案:
1. 针对 生产者到交换机
confirm 机制 确定生产者将消息投递到交换机
2. 针对 交换机到队列
return 机制 交换机发送消息到队列失败会执行回调
3. 针对 队列到消费者
消息没发 --> 消息持久化
消息发了,不知道是否被消费 ---> 手动确认 ack
## Confirm&Return机制
```
rabbitmq:
publisher-confirm-type: correlated # 启动confirm机制
publisher-returns: true # 启动return机制
```
配置类添加
```
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
//设置confirm机制的回调
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if (ack) {
log.info("消息{}发送到Exchange成功!!!",correlationData);
} else {
log.error("消息{}发送到Exchange失败,原因:{}",correlationData,cause);
}
}
});
//设置return机制的回调
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
//交换机发送消息到队列失败回调
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
log.error("消息发送到Queue失败!{}",message);
}
});
return rabbitTemplate;
}
```
生产者发送消息,设置消息的id
```
rabbitTemplate.convertAndSend(RabbitMQConfig.COURSE_EXCHANGE,
RabbitMQConfig.KEY_COURSE_SAVE, JSON.toJSONString(course),
new CorrelationData(UUID.randomUUID().toString()))
```
6.6 消息幂等性?
重复消费问题:
队列发送消息给消费者,因为网络原因,消费者没有收到,队列重复发送,网络恢复后会发送多个相同的消息给消费者
可能导致问题:
重复添加数据等非幂等性业务问题
非幂等性业务(每次操作获得不同结果,如:添加)
幂等性业务(每次操作结果相同,如:更新、删除)
消息有唯一id,保存redis,手动确认
解决方案:
1. 给每个消息添加id
2. 消费者获得消息后,通过id查询redis,判断该id是否存在
3. 如果不存在,就修改该id的value为1,执行业务,进行手动确认
4. 如果存在,就不执行业务
Redis的setnx命令
setnx key value
如果该键不存在,就设置键和值,返回1
如果该键存在,直接返回
7.1拿到需求你会怎么做?
首先将需求和自己的领导对一下,自己要干嘛,写那几个接口,那个数据库里面那几张表
然后分析表之间的关系,画出逻辑题以及要查询的有效字段。
然后分析sql,分析业务。
写完sql完成50%,开始写service层代码(各种各样的校验)。
写完接口,自己测试,生成测试集合
推送接口给前端或者Fuller对接口
7.2你会独立处理自己写的bug嘛?
debug 你必须会,不会建议重新学完debug在学java里面的idea
7.3当你要用没有用的知识点来做项目(你没学)你准备几天完成工作?
首先要分析是用啥东西来搞,类似于评估项目周期。然后给你自己定个目标(结合自己的学习能力看)。
比如郭龙2天学完python(学到java中的ssm)就是把基础过完两天。然后找几个类似的项目看看,分析为什么这么做。如果是你,你会这么做。
是一种特殊的数据,保存实际数据的位置,通过索引能快速定位到实际的数据,提高查询速度。
如:书籍目录、楼层索引
优点:大大提高查询速度
缺点:
1) 占用存储空间
2) 降低增删改数据
3) 创建时比较耗时
普通索引
建在某一个列上,提高按该列查询速度,没有特别约束,表中可以建多个
语法:
create index 索引名 on 表名(列名);
主键索引
建主键时,同时创建,约束是不能重复、不能为空、表只能有一个
唯一索引
建在某一个列上,提高按该列查询速度同时该列不能重复,表中可以建多个
create unique index 索引名 on 表名(列名);
全文索引
用于长文本的列(text)
create fulltext index 索引名 on 表名(列名);
组合索引
用于多个列的查询
create index 索引名 on 表名(列名1,列名2,列名3);
最左前缀原则:把最重要的列放到左边
查询时如果没有最左边的列,索引就失效
列1生效
列1,列2 生效
列1,列2,列3 生效
列2,列3 失效
explain 查询语句;
分析查询语句,是否能正常使用索引
最重要的:type、key、ref
使用场景
1. 数据量特别大(百万级别)
2. 列没有太多空值
3. 列没有很多重复
4. 列经常用于查询和排序
失效情况
B-Tree和B+Tree
B-Tree属于平衡搜索多叉树,分为根节点、枝节点、叶子节点,每个节点由:键、数据、指针组成
指针指向下面的子节点,搜索时采用二分查找,效率比较高。
查询效率和树的高度(高度越高、效率越低)
B+Tree是B-Tree升级版
将数据全部放到叶子节点,非叶子节点只保存键和指针,磁盘块就能保存更多节点,降低了树的高度,大大提升了查找效率。
百度
https://blog.csdn.net/l1394049664/article/details/81814090?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165595057616781432931223%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=165595057616781432931223&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_positive~default-1-81814090-null-null.142v20control,157v15new_3&utm_term=mysql%E4%BA%8B%E5%8A%A1%E7%9A%84%E5%9B%9B%E5%A4%A7%E7%89%B9%E6%80%A7&spm=1018.2226.3001.4187
系统规模大,用户量、数据量大
- MySQL集群,多台MySQL服务器
- 分库分表
垂直分库
将相关的表,放入不同的数据库,如将电商数据库分为:订单数据库、库存数据库
水平分库
将一个数据库的数据,分开存储到不同的服务器上
垂直分表
将表的字段分开,放到不同的表中
如:商品表分为商品和详情表
某些数据在分页中查看,某些大的数据text\blob放详情表
水平分表
数据量特别大,会严重影响查询
将一个表的数据分到多张表中
主从复制
多台MYSQL服务器,分为主(master)和从(slave)服务器
一主多从,多主多从
- 读写分离
建立在MySQL主从复制的基础上,分为主数据库和从数据库
主数据库负责写(增删改)从数据库负责读(查询)
主数据库写入数据后,会记录到bin-log文件中,从数据库会将bin-log中的数据同步过来
使用数据库中间件:Sharding-jdbc、myCat等
范式
优点: 规范数据库设计,消除冗余,方便数据的修改
缺点: 降低查询效率
反范式 在表中加入冗余字段
提高查询效率
表的设计按具体情况范式和反范式结合使用
选用合适的存储引擎
存储引擎是数据库中存储数据的方式,如存储的位置、存储约束、数据结构等的结合
不同的存储引擎有不同的功能和性能
常用存储引擎:
不同点:
InnoDB | MyIsam | |
---|---|---|
事务 | 支持 | 不支持 |
查询性能 | 以前低于MyIsam,目前差不多 | 快 |
锁 | 支持表锁和行锁,并发性能高 | 支持表锁 |
外键 | 支持 | 不支持 |
行数保存 | 不保存,需要用count(*) | 保存查询数据 |
索引 | 非聚簇索引(索引和实际数据不在一起) | 聚簇索引(索引和实际数据在一起) |
一般情况下使用InnoDB
字段优化
主键
必须给表设置主键id
尽量不用业务字段,使用没有意义字段如(int自增)
int自增效率高于UUID
数据类型
字符串 尽量使用varchar,不使用char (varchar存储空间更小,数据少查询效率高)
尽量使用小的数据类型,如:性别 varchar(1) 男 女 --> int 1 0 --> small int --> tiny int (1字节 -128~127)
尽量加not null约束
1) 索引
数据量大,使用索引
介绍索引的应用场景、分类
2) 缓存
Redis缓存、MyBatis缓存
3) 使用连接池
Druid\Hikari\c3p0\dbcp
4) 分页查询
1. 查询之前,使用explain查看执行计划
2. 尽量避免select *
3. 尽量避免where中使用<>和!=
4. 尽量避免where中使用is null和is not null
5. 尽量避免where中列避免使用函数和表达式 where 1 + age = 20 where max(age) = 20
6. 尽量避免模糊查询时,通配符在前面,如 name like ‘%xx’
7. 尽量使用exists代替in
尽量避免where中使用or,union 代替or
select name from student where age = 20 or address =‘武汉’
==>
select name from student where age = 20
union
select name from student where address =‘武汉’
看具体的业务场景
看自己能力
drop table if exists student;
create table student(
stu_id int primary key auto_increment,
stu_name varchar(20),
stu_gender varchar(20),
stu_age int
);
drop table if exists course;
create table course(
course_id int primary key auto_increment,
course_name varchar(20)
);
drop table if exists score;
create table score(
score_id int primary key auto_increment,
stu_id int,
course_id int,
score int,
constraint fk_stu_id foreign key (stu_id) references student(stu_id),
constraint fk_course_id foreign key (course_id) references course(course_id)
);
insert into student(stu_name,stu_gender,stu_age)
values('张三','男',15),('李四','男',15),('王五','男',15),('赵六','男',15);
insert into course(course_name)
values('语文'),('数学'),('英语');
insert into score(stu_id,course_id,score)
values(1,1,80),(1,2,82),(1,3,84),(2,1,60),
(2,2,70),(2,3,86),(3,1,83),(3,2,77),(3,3,89);
原始数据:
学号 姓名 课程 成绩
001 张三 语文 60
001 张三 数学 89
001 张三 英语 88
002 李四 语文 88
002 李四 数学 66
002 李四 英语 90
行转列的效果:
学号 姓名 语文 数学 英语
001 张三 60 89 88
002 李四 88 66 90
1) 完成多表连接
-- 1. 三表连接
select s.stu_id 学号,s.stu_name 姓名,c.course_name 课程,sc.score
from student s left join score sc on s.stu_id = sc.stu_id
left join course c on c.course_id = sc.course_id;
2) 将课程名称作为列
使用Case End语句
语法:
case 列名
when 值1 then 结果1
when 值2 then 结果2
else 缺省结果
end
case
when 条件1 then 结果1
when 条件2 then 结果2
else 缺省结果
end
-- 2. 使用case-END
select s.stu_id 学号,s.stu_name 姓名,
case c.course_name
when '语文' then sc.score
else 0
end 语文,
case c.course_name
when '数学' then sc.score
else 0
end 数学,
case c.course_name
when '英语' then sc.score
else 0
end 英语
from student s left join score sc on s.stu_id = sc.stu_id
left join course c on c.course_id = sc.course_id;
-- 4. 求最大值或总和
select s.stu_id 学号,s.stu_name 姓名,
max(
case c.course_name
when '语文' then sc.score
else 0
end) 语文,
max(case c.course_name
when '数学' then sc.score
else 0
end) 数学,
max(case c.course_name
when '英语' then sc.score
else 0
end) 英语
from student s left join score sc on s.stu_id = sc.stu_id
left join course c on c.course_id = sc.course_id
group by s.stu_id;
完成多表连接
-- 1. 三表连接
select s.stu_id 学号,s.stu_name 姓名,c.course_name 课程,sc.score
from student s left join score sc on s.stu_id = sc.stu_id
left join course c on c.course_id = sc.course_id;
2) 将课程名称作为列
使用Case End语句
语法:
case 列名
when 值1 then 结果1
when 值2 then 结果2
else 缺省结果
end
case
when 条件1 then 结果1
when 条件2 then 结果2
else 缺省结果
end
-- 2. 使用case-END
select s.stu_id 学号,s.stu_name 姓名,
case c.course_name
when '语文' then sc.score
else 0
end 语文,
case c.course_name
when '数学' then sc.score
else 0
end 数学,
case c.course_name
when '英语' then sc.score
else 0
end 英语
from student s left join score sc on s.stu_id = sc.stu_id
left join course c on c.course_id = sc.course_id;
-- 4. 求最大值或总和
select s.stu_id 学号,s.stu_name 姓名,
max(
case c.course_name
when '语文' then sc.score
else 0
end) 语文,
max(case c.course_name
when '数学' then sc.score
else 0
end) 数学,
max(case c.course_name
when '英语' then sc.score
else 0
end) 英语
from student s left join score sc on s.stu_id = sc.stu_id
left join course c on c.course_id = sc.course_id
group by s.stu_id;