黑马点评DAY5|商户查询缓存

商户查询缓存

缓存的定义

缓存就是数据交换的缓冲区(Cache),是存储数据的临时地方,一般读写性能较高。

  • 比如计算机的CPU计算速度非常快,但是需要先从内存中读取数据再放入CPU的寄存器中进行运算,这样会限制CPU的运算速度,所以CPU中也会设计一个缓存,存入经常需要用到的数据,提升了运算效率。CPU缓存也是衡量CPU性能好坏的重要标准之一。
  • 再比如浏览器缓存,会缓存一些页面静态资源(js、css),浏览器缓存未命中的一些数据就会去Tomcat中的Java应用请求,而Java应用也有应用层缓存,一般用Redis去做。如果缓存再没有命中,就可以去数据库查询,数据库也有缓存,mysql中如索引数据。最后还会去查询CPU缓存,磁盘缓存。在这里插入图片描述

缓存的优缺点

优点:

  • 降低了后端的负载,实际开发的过程中,企业的数据量,少则几十万,多则几千万,如果没有缓存来作为避震器,这么大的用户并发量服务器是扛不住的。
  • 缓存的读写效率非常高,响应时间短

缺点:

  • 数据一致性成本高
  • 代码维护成本高,解决一致性问题需要复杂的业务编码,也有可能出现缓存穿透、缓存雪崩等问题
  • 运维成本,缓存需要大规模集群模式,需要人力成本

给店铺查询任务添加缓存

整体的业务逻辑如下图所示:
在这里插入图片描述

  • 先从redis中通过店铺id查询缓存数据,登录模块是用map存的,这里我们使用String来存,就需要将对象先转为JSON格式。
  • 如果redis中存在,就返回店铺信息。
  • 如果redis中不存在,就继续向数据库中查询。
  • 如果数据库不存在,返回“店铺不存在”
  • 如果数据库存在,将店铺信息写入redis
  • 返回店铺信息
    代码如下:
package com.hmdp.service.impl;import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.dto.Result;
import com.hmdp.entity.Shop;
import com.hmdp.mapper.ShopMapper;
import com.hmdp.service.IShopService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;import static com.hmdp.utils.RedisConstants.CACHE_SHOP_KEY;/*** <p>*  服务实现类* </p>** @author 虎哥* @since 2021-12-22*/
@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {@AutowiredStringRedisTemplate  stringRedisTemplate;@Overridepublic Result queryById(Long id) {String key = CACHE_SHOP_KEY + id;//1.从redis中查询String shopJson = stringRedisTemplate.opsForValue().get(key);//2.存在,返回店铺信息if (!StringUtils.isBlank(shopJson)) {return Result.ok(JSONUtil.toBean(shopJson, Shop.class));}//3.不存在,用id在数据库查询Shop shop = getById(id);//4.不存在,返回“店铺不存在”if (shop == null) {return Result.ok("店铺不存在");}//5.存在,缓存到redisstringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop));//6.返回店铺信息return Result.ok(shop);}
}

然后再去第二次查询某一个美食的数据,发现速度由2ms变成了1ms。
在resp中也发现了cache:shop:id的缓存。
在这里插入图片描述

拓展练习

将首页的店铺种类信息缓存到redis中
在这里插入图片描述
因为店铺种类有十种,可以通过LIst的数据结构存储,但是需要将List中的ShopType对象先转为JSON,取出的时候再由JSON转为ShopType对象。具体代码如下:

package com.hmdp.service.impl;import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.dto.Result;
import com.hmdp.entity.ShopType;
import com.hmdp.mapper.ShopTypeMapper;
import com.hmdp.service.IShopTypeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;import java.util.ArrayList;
import java.util.List;import static com.hmdp.utils.RedisConstants.CACHE_SHOP_TYPE_KEY;/*** <p>*  服务实现类* </p>** @author 虎哥* @since 2021-12-22*/
@Service
public class ShopTypeServiceImpl extends ServiceImpl<ShopTypeMapper, ShopType> implements IShopTypeService {@AutowiredStringRedisTemplate stringRedisTemplate;public Result queryTypeList() {//1.从redis中查找店铺类型数据List<String> shopTypesByRedis = stringRedisTemplate.opsForList().range(CACHE_SHOP_TYPE_KEY, 0, 9);//2.存在,返回店铺信息,最终需要返回List<ShopType>形式的list,因此需要将JSON转换为ShopType类型List<ShopType> shopTypes = new ArrayList<>();if(shopTypesByRedis.size() != 0){for(String s:shopTypesByRedis){//转为JSONShopType shoptype = JSONUtil.toBean(s, ShopType.class);shopTypes.add(shoptype);}return Result.ok(shopTypes);}//3.不存在,去数据库中寻找,并根据sort排序List<ShopType> shopTypesByMysql = query().orderByAsc("sort").list();//4.数据库不存在,返回店铺信息不存在if(shopTypesByMysql.size() == 0){return Result.ok("店铺信息不存在");}//5.店铺信息存在,存入redis中for(ShopType shop:shopTypesByMysql){stringRedisTemplate.opsForList().leftPush(CACHE_SHOP_TYPE_KEY, JSONUtil.toJsonStr(shop));}//6.返回店铺信息return Result.ok(shopTypesByMysql);}
}

缓存更新策略

在业务中,如果我们对数据库数据做了一些修改,但是缓存中的数据没有保持同步更新,用户查询时会查到缓存中的旧数据,这在很多场景下是不允许的。缓存更新的几种策略有三种

  • 内存淘汰(该机制默认存在)
    缓存设定一定的上限,当达到这个上限就会自动淘汰部分数据。一致性保持较差,因为淘汰的这一部分数据才可以更新,维护成本为0.
  • 超时剔除
    通过redis中的expire关键字添加TTL时间,到期后自动删除缓存。
    一致性强弱取决于TTL的时间,一致性一般好于内存淘汰机制。维护成本也不是很高。
  • 主动更新 <\font>
    自己编写业务逻辑,在修改数据库的同时,更新缓存。
    一致性好,但是维护成本较高。

业务场景选择更新策略的原则:

  • 低一致性需求:使用内存淘汰机制,例如店铺类型的查询缓存
  • 高一致性需求:主动更新,并以超时剔除作为兜底方案。如店铺详情查询。
    一般采用01的方式主动更新缓存

在这里插入图片描述
主动更新的方法可以采用:当数据库发生改变的时候,删除缓存,当查询数据库的时候,更新缓存。
这里有两种操作顺序的选择:

  • 先删除缓存,再操作数据库,但是有可能发生如下图左图的安全问题。
  • 先操作数据库,再删除缓存。有可能发生如下图右图的安全问题。
  • 但是因为数据库读写时间远远大于缓存读写时间,因此右图发生的概率更低。万一发生,超时时间可以兜底。
    在这里插入图片描述

业务修改

  • 根据id查询商铺信息,如果未能在缓存命中,从数据库查询,并写入缓存,设置超时时间
   //5.存在,缓存到redis,加入有效时间限制stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);
  • 根据id修改商铺信息,先修改数据库,再删除缓存。这两个动作需要绑定,所以该方法用事务控制其原子性
    @Override@Transactionalpublic Result update(Shop shop) {Long id = shop.getId();if(id == null){return Result.fail("店铺id不为空");}//1.更新数据库updateById(shop);//2.删除缓存stringRedisTemplate.delete(CACHE_SHOP_KEY+shop.getId());return Result.ok();}

测试:

  • 首先测试当访问某一家店铺信息的时候,未命中,是否会缓存到redis中
  • 再测试修改店铺信息是否会删除redis缓存,因为修改的功能只能在商家界面做,所以这里用http-client对业务逻辑进行测试。==发送请求,数据库修改,redis缓存也被删除。==说明业务修改成功。这样可以有效解决一致性问题。
PUT http://localhost:8081/shop
Content-Type: application/json{"area":"大关","openHours": "10:00-22:00","sold": 4215,"address": "china","comments":3035,"avgPrice": 80,"score": 37,"name": "110茶餐厅","typeId": 1,"id": 1
}

缓存穿透

客户端请求的数据在缓存和数据库中都不存在,那么根据我们的缓存更新策略,最终都会向数据库索取数据;那么如果有不怀好意的人用并发的线程用虚假的id向数据库请求数据,就会搞垮数据库。
两种解决方案:

  • 缓存空对象:如果redis和数据库中都未能命中,最终数据库会向redis写入一个null,这样在下一次向redis请求的时候就不会再到达数据库。
    优点:实现简单、维护方便
    缺点:
    • 有额外的内存消耗(但是也可以给null设置一个TTL
    • 可能造成短期的不一致(可以控制TTL的时长
      在这里插入图片描述
  • 布隆过滤器
    • 布隆过滤器的原理
      • 定义:布隆过滤器(Bloom Filter)是一种空间效率非常高的概率型数据结构,用于判断一个元素是否属于某个集合。
      • 构成
        1.布隆过滤器使用一个固定长度的位数组,所有位初始都设置为0。
        2.一组独立的哈希函数,用于将输入元素映射到位数组中的某个位置。
      • 判断原理:向布隆过滤器中添加元素时,通过k个哈希函数计算出k个位置,并将这些位置上的位设置为1。查询元素时,使用同样的哈希函数计算出k个位置,并检查这些位置上的位是否全为1。如果所有位置都为1,则元素可能在集合中如果有一个位置为0,则元素肯定不在集合中
    • 优点:内存占用小,没有多余的key
    • 缺点:实现复杂、存在误判可能

在这里插入图片描述

采用缓存空对象解决缓存穿透问题

我们应该做如下修改:
在这里插入图片描述
我们需要修改queryById方法,注意字符串判断内容是否相等用equals:shopJson.equals(“”)

   @Overridepublic Result queryById(Long id) {String key = CACHE_SHOP_KEY + id;//1.从redis中查询String shopJson = stringRedisTemplate.opsForValue().get(key);//2.命中,返回店铺信息if (!StringUtils.isBlank(shopJson)) {return Result.ok(JSONUtil.toBean(shopJson, Shop.class));}//如果命中的是"",就返回"店铺信息不存在!"if(shopJson != null){return Result.fail("店铺信息不存在!");}//3.不存在,用id在数据库查询Shop shop = getById(id);//4.不存在,返回“店铺不存在”if (shop == null) {//如果数据库不存在该id的商铺,就向redis中存入空字符串,并返回"店铺信息不存在!"stringRedisTemplate.opsForValue().set(key, "");return Result.ok("店铺信息不存在!");}//5.存在,缓存到redis,加入有效时间限制stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);//6.返回店铺信息return Result.ok(shop);}

然后进行测试,发送请求:http://localhost:8080/api/shop/1111,id=1111并不存在,但是该数据会被缓存到redis中:
在这里插入图片描述再次发送这个请求,不会到达数据库,而是访问redis之后直接就返回。控制台也没有任何数据库调用的日志打印出来。
在这里插入图片描述
其他解决方案:

  • 增加id的复杂度,让攻击者无发猜测到id格式。
  • 对id做一些基础的格式校验
  • 加强用户权限的管理
  • 做好热点参数的限流

缓存雪崩

缓存雪崩指的是大量可以在同一时段同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。
在这里插入图片描述
解决方案:

  • 给不同的key的TTL添加随机值,这样key就不会在同一时间宕机
  • 提高==Redis集群(Redis哨兵模式)==服务的可用性。当一个Redis挂了,会被监控到,立马启动另外一个Redis提供服务。也可以使用主从结构构成集群,防止主节点的数据丢失。
  • 给缓存业务添加降级限流策略(比如快速失败、拒绝服务)
  • 给业务添加多级缓存(Nginx缓存–JVM缓存–Redis缓存–数据库缓存)。

缓存击穿

缓存击穿也叫热点Key问题,就是一个被高并发访问(比如正在做活动的某一件商品)并且缓存建立业务较为复杂的key失效了,突然大量的请求会在瞬间给数据库带来巨大的冲击。
两种解决方案:

互斥锁解决缓存击穿

让多线程只有一个线程能获取锁来创建缓存
在这里插入图片描述
在这里插入图片描述
我们可以手动地设定一个锁来实现这样的功能,redis中的setnx表示只有当一个key不存在的时候才可以写入,那么这样就可以达到互斥的效果。那么,

  • 获取锁的操作就是:setnx lock 1通常还会给锁加一个TTL,如果超过这个时间,就自动删除锁。防止获取到锁的线在这里插入代码片程出问题。
  • 释放锁的操作就是:del lock
    在这里插入图片描述
    代码实现:
  • 首先定义获取锁和释放锁的方法:
    获取锁:
    private boolean tryLock(String key){//获取锁Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10L, TimeUnit.SECONDS);return BooleanUtil.isTrue(flag);}

释放锁:

    private void unLock(String key){stringRedisTemplate.delete(key);}

将缓存穿透的业务逻辑封装,最终返回Shop对象

  //解决缓存穿透的代码public Shop queryWithPassThroough(Long id){String key = CACHE_SHOP_KEY + id;//1.从redis中查询String shopJson = stringRedisTemplate.opsForValue().get(key);//2.命中,返回店铺信息if (!StringUtils.isBlank(shopJson)) {return JSONUtil.toBean(shopJson, Shop.class);}//如果命中的是"",就返回"店铺信息不存在!"if(shopJson != null){return null;}//3.不存在,用id在数据库查询Shop shop = getById(id);//4.不存在,返回“店铺不存在”if (shop == null) {//如果数据库不存在该id的商铺,就向redis中存入空字符串,并返回"店铺信息不存在!"stringRedisTemplate.opsForValue().set(key, "");return shop;}//5.存在,缓存到redis,加入有效时间限制stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);//6.返回店铺信息return shop;}

用互斥锁解决缓存击穿的问题:

    //用互斥锁解决缓存击穿的问题public Shop queryWithMutex(Long id){String key = CACHE_SHOP_KEY + id;//1.从redis中查询String shopJson = stringRedisTemplate.opsForValue().get(key);//2.命中,返回店铺信息if (!StringUtils.isBlank(shopJson)) {return JSONUtil.toBean(shopJson, Shop.class);}//3.如果命中的是"",就返回"店铺信息不存在!"if(shopJson != null){return null;}Shop shop = null;//4.实现缓存重建//4.1 获取互斥锁String lockKey = "lock:shop:" + id;boolean isLock = tryLock(lockKey);try {//4.2 判断是否获取成功//4.3 失败,则休眠并重试if(!isLock){Thread.sleep(50);return queryWithMutex(id);}//4.4 成功,用id在数据库查询shop = getById(id);//5.不存在,返回“店铺不存在”if (shop == null) {//如果数据库不存在该id的商铺,就向redis中存入空字符串,并返回"店铺信息不存在!"stringRedisTemplate.opsForValue().set(key, "");return shop;}//6.存在,缓存到redis,加入有效时间限制stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);//7.释放互斥锁} catch (InterruptedException e) {throw new RuntimeException(e);}finally{unLock(lockKey);}//8.返回店铺信息return shop;}
  • 通过自动化测试工具jmeter对进行压力测试
    在这里插入图片描述
    执行完发现没有报错
    在这里插入图片描述
    并且数据库只调用了一次select操作,说明互斥锁成功实现了
    在这里插入图片描述

设置逻辑过期时间解决缓存击穿

在这里插入图片描述

  • 缓存工具封装对象:
    先定义一个类用来保存以及超时时间,对原来代码没有侵入性。
package com.hmdp.entity;import lombok.Data;import java.time.LocalDateTime;/*** @author Zonda* @version 1.0* @description TODO* @2024/7/4 16:21*/
@Data
public class RedisData {private LocalDateTime expireTime;private Object data;}

在ShopServiceImpl 新增此方法,利用单元测试进行缓存预热

    public void saveShop2Redis(Long id,Long expireSeconds){Shop shop = getById(id);RedisData redisData = new RedisData();redisData.setData(shop);redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(redisData));}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/40603.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C语言】union 关键字

在C语言中&#xff0c;union关键字用于定义联合体。联合体是一种特殊的数据结构&#xff0c;它允许不同的数据类型共享同一段内存。所有联合体成员共享同一个内存位置&#xff0c;因此联合体的大小取决于其最大成员的大小。 定义和使用联合体 基本定义 定义一个联合体类型时…

SiCat:一款多功能漏洞利用管理与搜索工具

关于SiCat SiCat是一款多功能漏洞利用管理与搜索工具&#xff0c;该工具基于纯Python 3开发&#xff0c;旨在帮助广大研究人员有效地识别和收集来自开源和本地存储库的漏洞信息。 SiCat专注于网络安全管理方面的实践工作&#xff0c;允许研究人员快速实现在线搜索&#xff0c;…

2024亚太赛(中文赛)数学建模竞赛选题建议+初步分析

提示&#xff1a;DS C君认为的难度&#xff1a;B<C<A&#xff0c;开放度&#xff1a;C<A<B。 综合评价来看 A题适合有较强计算几何和优化能力的团队&#xff0c;难度较高&#xff0c;但适用面较窄。 B题数据处理和分析为主&#xff0c;适合数据科学背景的团队…

Android TextView的属性与用法

文本控件包括TextView、EditText、AutoCompleteTextView、CheckedTextView、MultiAutoCompleteTextView、TextInputLayout等&#xff0c;其中TextView、EditText是最基本最重要的文本控件&#xff0c;是必须要掌握的文本控件。 1.TextView TextView控件用于显示文本信息&…

自然语言处理学习--3

对自然语言处理领域相关文献进行梳理和总结&#xff0c;对学习的文献进行梳理和学习记录。希望和感兴趣的小伙伴们一起学习。欢迎大家在评论区进行学习交流&#xff01; 论文&#xff1a;《ChineseBERT: Chinese Pretraining Enhanced by Glyph and Pinyin Information》 下面…

原厂商是什么意思?云管平台原厂商有哪些企业?

最近不少IT小伙伴在问关于原厂商相关问题&#xff0c;今天我们就来简单回答一下&#xff0c;仅供参考&#xff01; 原厂商是什么意思&#xff1f; 原厂商&#xff0c;或称原厂&#xff0c;是指生产特定产品或零部件的原始厂家。 软件原厂商是什么意思&#xff1f; 软件原厂…

QT截屏,截取控件为图片,指定范围截屏三种截屏方式

项目中我们常用到截取屏幕&#xff0c;Qt给我的们多种方式&#xff1a; 主要有以下三种&#xff1a; 截取全屏&#xff1b;截取控件为图片&#xff1b;指定位置截屏三种截屏方式&#xff1b; 1.截取全屏 常用&#xff1a; 实现&#xff1a; QScreen *screen QGuiApplicat…

数据结构(一)C语言补

数据结构 内存空间划分 一个进程启动后&#xff0c;会生成4G的内存空间 0~3G是用户空间(应用层) 3~4G是内核空间(底层) 0~3G 3~4G 所有的进程都会共享3G~4G的内核空间&#xff0c; 但是每个进程会独立拥有0~3G的用户空间。 栈区 存放数据特点 栈区存放数据的申请空间的先后…

面试篇-Redis-2+持久化+过期key删除+内存淘汰

文章目录 前言一、你知道Redis 数据是怎么持久化的1.1 Redis 持久化的方式Rdb&#xff1a;1.1.1 主动备份save 命令&#xff1a;1.1.2 Redis 中使用bgsave 进行Rdb 的持久化 &#xff1a; 1.2 Redis 持久化的方式Aof&#xff1a;1.2.1 使用AOF 模式进行数据存储&#xff1a;1.2…

明星代言方式8种助力品牌占领市场-华媒舍

1. 明星代言的重要性和市场价值 明星代言是一种常见的品牌推广方式&#xff0c;通过联系知名度高的明星来推广产品或服务&#xff0c;从而提升品牌的知名度和美誉度。明星代言能够借助明星的影响力和粉丝基础&#xff0c;将品牌信息传达给更广泛的受众&#xff0c;从而提高销量…

Web Based Quiz System v1.0 SQL 注入漏洞(CVE-2022-32991)

前言 CVE-2022-32991 是一个影响 Web Based Quiz System v1.0 的 SQL 注入漏洞。这个漏洞存在于 welcome.php 文件中的 eid 参数处。攻击者可以通过此漏洞在数据库中执行任意 SQL 语句&#xff0c;从而获取、修改或删除数据库中的数据。 具体细节如下&#xff1a; 攻击向量&…

Es结合springboot(笔记回忆)

导包 <!--导入es--> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency> <dependency><groupId>org.springframework.boot<…

【代码随想录】【算法训练营】【第53天】 [739]每日温度 [496]下一个更大元素I [503]下一个更大元素II

前言 思路及算法思维&#xff0c;指路 代码随想录。 题目来自 LeetCode。 day 48&#xff0c;周六&#xff0c;不能再坚持~ 题目详情 [739] 每日温度 题目描述 739 每日温度 解题思路 前提&#xff1a;寻找任一个元素的右边比自己大的元素的位置 思路&#xff1a;通常…

软信天成:您的数据仓库真的“达标”了吗?

在复杂多变的数据环境中&#xff0c;您的数据仓库是否真的“达标”了&#xff1f;本文将深入探讨数据仓库的定义、合格标准及其与数据库的区别&#xff0c;帮助您全面审视并优化您的数据仓库。 一、什么是数据仓库&#xff1f; 数据仓库是一个面向主题的、集成的、相对稳定的、…

一个R包完成单细胞基因集富集分析 (全代码)

singleseqgset是用于单细胞RNA-seq数据的基因集富集分析的软件包。它使用简单的基础统计量&#xff08;variance inflated Wilcoxon秩和检验&#xff09;来确定不同cluster中感兴趣的基因集的富集。 Installation library(devtools) install_github("arc85/singleseqgse…

iOS手机竖着拍的照片被旋转了90°的原因以及解决方案

EXIF.getData(IMG_FILE, function () { // IMG_FILE为图像数据 var orientation EXIF.getTag(this, “Orientation”); console.log(“Orientation:” orientation); // 拍照方向 }); 获取拍照方向的结果为1-8的数字&#xff1a; 注意&#xff1a;对于上面的八种方向中&a…

2024.7.4作业

1.梳理笔记(原创) 2. 终端输入一个日期&#xff0c;判断是这一年的第几天 scanf("%d-%d-%d",&y,&m,&d); 闰年2月29天&#xff0c;平年2月28天 #include <stdio.h> int main(int argc,const char *argv[]) { int y0,m0,d0,sum0,i0; …

数据库表导出到excel:前置知识1 ALL_TAB_COLS

ALL_TAB_COLS 当前用户可访问的表、视图和群集的列的相关信息 其中几个字段: OWNER&#xff1a;表&#xff0c;视图及群集的Owner   TABLE_NAME&#xff1a; 表&#xff0c;视图及聚簇的名称   COLUMN_NAME&#xff1a; 字段名   DATA_TYPE &#xff1a;字段的数据类型…

万字长文MySQL Binlog 详细指南

目录 第一阶段 MySQL Binlog 基础用法1. Binlog基本概念1.1 什么是Binlog1.2 Binlog的作用1.3 Binlog格式 2. 配置和管理Binlog2.1 开启Binlog2.2 设置Binlog文件大小和保留时间2.3 查看Binlog状态 3. Binlog的实际应用3.1 数据恢复3.2 主从复制3.3 审计 4. Binlog工具使用4.1 …

收银系统源码-收银台营销功能-购物卡

1. 功能描述 购物卡&#xff1a;基于会员的电子购物卡&#xff0c;支持设置时效、适用门店、以及可用商品&#xff1b;支持售卖和充值赠送&#xff0c;在收银台可以使用&#xff1b; 2.适用场景 会员充值赠送活动&#xff0c;例如会员充值1000元&#xff0c;赠送面值100元购…