Redis 常问知识

1.Redis 缓存穿透问题

缓存穿透:当请求的数据在缓存和数据库中不存在时,该请求就跳出我们使用缓存的架构(先从缓存找,再从数据库查找、这样就导致了一直去数据库中找),因为这个数据缓存中永远也不会存在。导致后续所有的这个请求(被恶意的人发现后)都会直接请求数据库。恶意用户一直发送该请求会导致数据库服务宕机。

解决方法常用的两种

一:缓存空数据,二,使用布隆过滤器进行校验。

缓存空数据

在数据库查询到不存在的数据时,对该数据进行缓存为空(可以设置稍短的3~5分钟的TTL),之后相同的请求,就会在缓存中查到,而不去请求数据库。

代码案列

java 复制代码
  /**
     * 查询商户信息
     * @param id
     * @return
     */
    @Override
    public Result queryById(Long id) {
        //查询缓存
        String string = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY+id);
        //hutool 工具类 符合条件"adc" 不符合条件"",null, "/t/n"
        if (StrUtil.isNotBlank(string)){
            Shop shop = JSONUtil.toBean(string, Shop.class);
            return Result.ok(shop);
        }
        //若是 " " 上面已经判断了不是"" 不是null ,
        if(string != null){
            return Result.fail("商户不存在");
        }

        // 缓存不存在 查数据库
        Shop shop = getById(id);
        if (shop ==null) {
            //将空值写入缓存
            stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
            return Result.fail("商户不存在");
        }

        //写入缓存
        stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+ id, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);

        return Result.ok(shop);
    }

优点

  • 实现简单

缺点

  • 缓存空值,会占用redis的内存空间(可以设置过期时间),可能会导致短期数据不一致问题(除非在新增数据的时候,删除redis中的数据)。

布隆过滤器
扩展

其实不仅仅是这两种解决缓存穿透的方案。

此外还可以

  • 增强id的复杂性,避免id被猜到规律。
  • 加强用户权限校验
  • 做好热点数据限流。

此外,还应该做好数据的校验,对于一些不符合业务逻辑数据的请求直接拦截掉,不在请求数据库。

还可以采用对接口进行限流。甚至黑名单封禁。

2.Redis的哨兵模式

复制代码
为了提高Redis的性能搭建主从集群后,当主节点出现问题,Redis服务就不可以进行写操作,服务就不可用,Redis提供了哨兵机制,来实现主从集群的自动故障恢复。	

哨兵的结构和作用
  • 监控:Sentinel 会不断检查您的master和slave是否按预期工作。
    • 具体来说就是Sentinel 一直发送ping,接收pang,说明该节点正常可用,反之就是主观下线,当多个Sentinel (一般是一半哨兵监控redis节点为不可用)检测一个redis节点都说明该节点不可用后,该节点是客观下线(服务不可用)。
  • 自动故障恢复:如果master故障,Sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主。
  • 通知:Sentinel充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis的客户端。

补充

Sentinel基于心跳机制监测服务状态,每隔1秒向集群的每个实例发送ping命令:

主观下线:如果某sentinel节点发现某实例未在规定时间响应,则认为该实例主观下线。

客观下线:若超过指定数量(quorum)的sentinel都认为该实例主观下线,则该实例客观下线。quorum值最好超过Sentinel实例数量的一半。

哨兵选主规则

首先判断主与从节点断开时间长短,如超过指定值就排该从节点

然后判断从节点的slave-priority值,越小优先级越高

如果slave-prority一样,则判断slave节点的offset值,越大优先级越高

最后是判断slave节点的运行id大小,越小优先级越高。

脑裂问题

当哨兵网络与Redis主节不在同一个网络下,监控就会出问题,但是Redis主节点并没有问题,服务仍在Redis主节点写,由于网络问题哨兵通过监控认为Redis主节点出现了问题,就会在从节点选一个做为主节点,这样就出现了两个主节点,这就是脑裂问题,当网络原因回复后,原来的主节点为变成从节点,以新的主节点为主,原来的主节点会同步新的主节点信息,就会导致数据丢失。

解决方法 redis.config

shell 复制代码
min-replicas-to-write 1   表示最少的salve节点为1个
min-replicas-max-lag 5  表示数据复制和同步的延迟不能超过5秒

3.Redis 主从复制,同步流程

复制代码
单节点的Redis服务并不可靠,并发有上限。
  • 如果服务器发生了宕机,由于数据恢复是需要点时间,那么这个期间是无法服务新的请求的;
  • 如果这台服务器的硬盘出现了故障,可能数据就都丢失了。为了提高Redis 服务的可靠性,以及高性能,采用集群模式------主从复制。在主节点进行写操作,在从节点进行读操作。

复制代码
具体的主从Redis节点的同步流程是这样子,分为首次同步,和增量同步。

首次同步,也就是全量同步

增量同步

  • Replication Id:简称replid,是数据集的标记,id一致则说明是同一数据集。每一个master都有唯一的replid,slave则会继承master节点的replid
  • offset:偏移量,随着记录在repl_baklog中的数据增多而逐渐增大。slave完成同步时也会记录当前同步的offset。如果slave的offset小于master的offset,说明slave数据落后于master,需要更新。

4.Redis持久化策略

复制代码
Redis是内存服务,但是提供了两个持久化策略AOF,RDB来持久化Redis的数据。

AOF 日志文件

Redis 每执行一条写操作命令成功后,就把该命令以追加的方式写入到一个文件里,然后重启Redis 的时候,先去读取这个文件里的命令,并且执行它,这不就相当于恢复了缓存数据了

在Redis中AOF持久化功能默认是不开启的,在redis.config文件中设置

AOF的记录命令的频率可以通过redis.config文件来配置

shell 复制代码
# 表示每执行一次写命令,立即记录到AOF文件
appendfsync always 
# 写命令执行完先放入AOF缓冲区,然后表示每隔1秒将缓冲区数据写到AOF文件,是默认方案
appendfsync everysec 
# 写命令执行完先放入AOF缓冲区,由操作系统决定何时将缓冲区内容写回磁盘
appendfsync no

因为是记录命令,AOF文件会比RDB文件大的多。而且AOF会记录对同一个key的多次写操作,但只有最后一次写操作才有意义。通过执行bgrewriteaof命令,可以让AOF文件执行重写功能,用最少的命令达到相同效果。

Redis也会在触发阈值时自动去重写AOF文件。阈值也可以在redis.conf中配置:

shell 复制代码
# AOF文件比上次文件 增长超过多少百分比则触发重写
auto-aof-rewrite-percentage 100
# AOF文件体积最小多大以上才触发重写 
auto-aof-rewrite-min-size 64mb 

RDB

RDB全称Redis Database Backup file(Redis数据备份文件),也被叫做Redis数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后,从磁盘读取快照文件,恢复数据.

Redis 服务默认开启。用户可以手动备份

shell 复制代码
redis -cli
save # 由Redis主进程来执行RDB,会阻塞所有的命令
bgsave # 开启子进程执行RDB,避免主进程收到影响

当然Redis内部有自动触发RDB的机制,在redis.config中

plain 复制代码
# 900秒内,如果至少有1个key被修改,则执行bgsave 
save 900 1  
save 300 10  
save 60 10000 
复制代码
这里提一点,Redis 的快照是全量快照,也就是说每次执行快照,都是把内存中的「所有数据」都记录到磁盘中。 所以可以认为,执行快照是一个比较重的操作,如果频率太频繁,可能会对 Redis 性能产生影响。如果频率太低,服务器故障时,丢失的数据会更 多。 通常可能设置至少 5 分钟才保存一次快照,这时如果 Redis 出现宕机等情况,则意味着最多可能丢失 5 分钟数据。 这就是 RDB 快照的缺点,在服务器发生故障时,丢失的数据会比 AOF 持久化的方式更多,因为 RDB 快照是全量快照的方式,因此执行的频率不能 太频繁,否则会影响 Redis 性能,而 AOF 日志可以以秒级的方式记录操作命令,所以丢失的数据就相对更少。

二者比较

这两种技术都会用各用一个日志文件来记录信息,但是记录的内容是不同的。

  • AOF 文件的内容是操作命令;
  • RDB 文件的内容是二进制数据。

RDB 快照就是记录某一个瞬间的内存数据,记录的是实际数据,而 AOF 文件记录的是命令操作的日志,而不是实际的数据。因此在 Redis 恢复数据时, RDB 恢复数据的效率会比 AOF 高些,因为直接将 RDB 文件读入内存就可以,不需要像 AOF 那样还需要额外执行操作 命令的步骤才能恢复数据

5.Redis与数据库数据一致性问题

复制代码
为了提高查询效率引入了redis作为缓存,但是出现了新的问题就是,缓存中的数据和数据库中的数据不一致问题。

解决数据不一致问题的方法,最简单的就是对缓存数据设置较短的过期时间,在过期时间后,会从数据库查询新的数据更新缓存,但是这种被动的等待过期时间,一致性是不符合大部分应用场景的。

业内的解决方案

  • Cache Aside Pattern 人工编码方式:缓存调用者在更新完数据库后再去更新缓存,也称之为双写方案。

  • Read/Write Through Pattern : 由系统本身完成,数据库与缓存的问题交由系统本身去处理。

  • Write Behind Caching Pattern :调用者只操作缓存,其他线程去异步处理数据库,实现最终一致。

    通常使用第一种方案,在更新数据库的同时更新缓存(删除缓存)。

    如果采用第一个方案,那么假设我们每次操作数据库后,都操作缓存,但是中间如果没有人查询,那么这个更新缓存动作实际上只有最后一次生效,中间的更新动作意义并不大,我们可以把缓存删除,等待再次查询时,将缓存中的数据加载出来。

我们需要考虑一下几点

  1. 在数据库更新时,缓存是更新还是删除?
    1. 更新缓存:每次更新数据库都更新缓存,无效写操作较多。
    2. 删除缓存:更新数据库时让缓存失效,查询时再更新缓存。
  2. 怎么确保数据库更新,缓存也更新(删除),
    1. 单体系统,将缓存与数据库操作放在一个事务
    2. 分布式系统,利用TCC等分布式事务方案
  3. 先操作缓存还是先操作数据库?

应该具体操作缓存还是操作数据库,我们应当是先操作数据库,再删除缓存,原因在于,如果你选择第一种方案,在两个线程并发来访问时,假设线程1先来,他先把缓存删了,此时线程2过来,他查询缓存数据并不存在,此时他写入缓存,当他写入缓存后,线程1再执行更新动作时,实际上写入的就是旧的数据,新的数据被旧数据覆盖了。

复制代码
先操作数据库在删除缓存,理论上也会出现问题,线程1查询在缓存中没有的数据,就会查询数据库将数据库查到的age =20,写入缓存,但在这时还未写入缓存,线程2,操作数据库age=21,删除缓存。此时线程1继续写入缓存age=20,又会出现不一致现象。但是在实际中并不太可能发生,因为写入缓存的时间是极快的。

- 先删除缓存,再操作数据库
- 先操作数据库,再删除缓存

6.Redis 缓存击穿

缓存击穿是指在高并发的情况下,当某个热点数据的缓存突然失效 (过期或被删除)并且缓存重建业务较复杂,大量请求直接穿透到后端数据库,导致数据库负载过高,甚至崩溃的问题。由于并发用户特别多,同时读缓存没读到数据,又去数据库中取数据,引起数据库压力瞬间增大。

解决方法:互斥锁构建缓存和逻辑过期时间

互斥锁构建缓存

在热点key失效后,加锁,确保只有一个线程查询数据库并构建缓存。其他的线程等待并重试在缓存中取值。

优点

  • 一致性高

缺点

  • 由于使用了锁,线程等待,性能低,还可以能出现死锁。

代码实现

java 复制代码
 /**
     * 获取锁
     * @param key
     * @return
     */
    private boolean tryLock(String key){
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 20, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);

    }

    /**
     * 释放锁
     * @param key
     */
    private  void unlock(String key){
        stringRedisTemplate.delete(key);
    }

/**
     * 查询商户信息 缓存击穿互斥锁
     * @param id
     * @return
     */
    public Shop queryWithMutex(Long id){
        String shopKey = CACHE_SHOP_KEY+ id;

        // 1. 从redis中查询店铺缓存
        String shopJson = stringRedisTemplate.opsForValue().get(shopKey);

        //2.判断是否命中缓存  isnotblank false: "" or "/t/n" or "null"
        if(StrUtil.isNotBlank(shopJson)){
            // 3.若命中则返回信息
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return shop;
        }
        //数据穿透判空   不是null 就是空串 ""
        if (shopJson != null){
            //返回错误信息
//            return  Result.fail("没有该商户信息(缓存)");
            return null;
        }
        //4.没有命中缓存,查数据库
        //todo :解决缓存击穿  不能直接查数据库。 利用互斥锁解决

        /**
         * 实现缓存重建
         * 1. 获取互斥锁
         * 2. 判断是否成功
         * 3. 失败就休眠重试
         * 4.成功 查数据库
         * 5 数据库存在该数据写入缓存
         * 6 不存在返回错误信息并写入缓存""
         * 7 释放锁
         *
         */

        //获取互斥锁 失败  休眠重试
        String lockKey = "lock:shop" + id;
        Shop shop=null;

        try {
            boolean isLock = tryLock(lockKey);
            //获取锁失败
            if (!isLock) {

                System.out.println("获取锁失败,重试");
                Thread.sleep(50);
                return queryWithMutex(id);//递归 重试
            }

            // 获取锁成功,再次检测缓存是否存在,存在就无需构建缓存,因为可能有的线程刚构建好缓存并释放锁,其他线程获取了锁
            //检测缓存是否存在  存在
            shopJson = stringRedisTemplate.opsForValue().get(shopKey);
            if (StrUtil.isNotBlank(shopJson)) {
                return JSONUtil.toBean(shopJson, Shop.class);
            }
            if (shopJson !=null){
                return null;
            }
            // 缓存不存在
            // 查数据库
             shop = super.getById(id);
            Thread.sleep(200);//模拟你测试环境 热点key失效模拟重建延迟
            if (shop == null){
                //没有该商户信息
                stringRedisTemplate.opsForValue().set(shopKey,"",CACHE_NULL_TTL,TimeUnit.SECONDS);
                return null;
            }
            //有该商户信息
            stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+ id, JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            unlock(lockKey);
        }
        return shop;

    }

逻辑过期时间

对热点key不设置过期时间,仅仅添加个expire 字段。

当使用该缓存时,根据过期字段判断是否更新缓存,若在期限内就直接使用改缓存,若不在期限内就更新缓存。

更新缓存的具体细节,利用锁确保一个线程重构缓存,防止数据库压力过大。在获得锁后,异步执行,新开线程执行重构缓存,同时原线程直接使用已经过期的数据。在此期间其他线程也发现缓存逻辑过期了,也会获得锁,但是获取锁失败,那就使用原来的老数据。

优点

  • 性能高

缺点

  • 数据不一致

代码实现

对类添加一个过期字段,为了满足开闭原则,可以自定义个新的类继承原来的类并添加expire字段,不过推荐如下写法

自定义个逻辑过期类,所有的逻辑过期类都可以使用(Object data 存原来的类)。

java 复制代码
/**
 * 逻辑过期类
 */
@Data
public class RedisData {
    private LocalDateTime expireTime;
    private Object data;
}

数据预热

java 复制代码
 /**
     * 添加逻辑过期时间
     * @param id
     * @param expireSeconds
     */
    public void savaShop2Redis(Long id ,Long expireSeconds){

        // 查询店铺数据
        Shop shop = getById(id);

        //封装逻辑过期时间
        RedisData redisData = new RedisData();
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));
        redisData.setData(shop);
        //写入redis
        stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+id,JSONUtil.toJsonStr(redisData));
    }

正式代码

java 复制代码
private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
public Shop queryWithLogicalExpire( Long id ) {
    String key = CACHE_SHOP_KEY + id;
    // 1.从redis查询商铺缓存
    String json = stringRedisTemplate.opsForValue().get(key);
    // 2.判断是否存在
    if (StrUtil.isBlank(json)) {
        // 3.存在,直接返回
        return null;
    }
    // 4.命中,需要先把json反序列化为对象
    RedisData redisData = JSONUtil.toBean(json, RedisData.class);
    Shop shop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);
    LocalDateTime expireTime = redisData.getExpireTime();
    // 5.判断是否过期
    if(expireTime.isAfter(LocalDateTime.now())) {
        // 5.1.未过期,直接返回店铺信息
        return shop;
    }
    // 5.2.已过期,需要缓存重建
    // 6.缓存重建
    // 6.1.获取互斥锁
    String lockKey = LOCK_SHOP_KEY + id;
    boolean isLock = tryLock(lockKey);
    // 6.2.判断是否获取锁成功
    if (isLock){
        CACHE_REBUILD_EXECUTOR.submit( ()->{

            try{
                //重建缓存
                this.saveShop2Redis(id,20L);
            }catch (Exception e){
                throw new RuntimeException(e);
            }finally {
                unlock(lockKey);
            }
        });
    }
    // 6.4.返回过期的商铺信息
    return shop;
}

7.内存淘汰策略(Redis内存满了怎么办)

Redis中,在配置文件有设置maxmemory大小,当超过这个大小,Redis回触发内存淘汰机制,默认的淘汰策略就是noeviction

Redis服务中提供的内存淘汰策略有八种

  • noeviction :它表示当运行内存超过最大设置内存时,不淘汰任何数据,而是不再提供服务,直接返回错误。
  • volatile-ttl:优先淘汰更早过期的键值。
  • allkeys-random: 对所有key随机删除。
  • volatile-random :对设置ttl的key随机删除
  • allkeys-lru :对所有最近最久使用的key随机删除
  • volatile-lru : 对设置ttl并且最近最久使用的key随机删除
  • allkeys-lfu : 所有使用最近最少使用的key随机删除
  • volatile-lfu 设置ttl的并且最近最少使用的key随机删除

8.Redis 缓存雪崩

缓存雪崩出现的原因是同一时间内大量的key同时失效或者Redis服务宕机,导致所有的请求都到数据库,数据库压力过大宕机。

根据产生雪崩的原因进行分析

key同时失效导致的雪崩

我们在做缓存预热时和添加缓存时,设置有效期的同时,额外的增加(1~3)的随机过期时间。

同时当key过期后,构建缓存,利用互斥锁构建缓存,防止数据库压力过大。

Redis服务宕机

利用Redis集群提高服务的可用性。

  • 哨兵模式
  • 集群模式

其他

给业务添加多级缓存, 如Guava或者Caffeine.

使用服务熔断或请求限流机制

我们可以启动服务熔断 机制,暂停业务应用对缓存服务的访问,直接返回错误,不用再继续访问数据库,从而降低对数据库的访问压力,保证数据库系统的正常运行,然后等到 Redis 恢复正常后,再允许业务应用访问缓存服务。

但是这样在Redis服务宕机期间所有的业务都不可用,为了减少对业务的影响,可以启用请求限流机制,只将少部分请求发送到数据库,

更多的请求就只能拒绝服务,等Redis服务正常后并缓存预热完成在解除请求限流机制

9.Redis 数据过期删除策略

Redis 对 key 设置过期时间后,需要有相应的机制将已过期的键值对删除,而做这个工作的就是过期键值删除策略。

Redis的过期策略:惰性删除和定期删除相配合使用。

惰性删除

在设置该key过期时间后,我们不去管它,当需要该key时,我们在检查其是否过期,如果过期,我们就删掉它,反之返回该key。

  • 优点 :对CPU友好,只会在使用该key时才会进行过期检查,对于很多用不到的key不用浪费时间进行过期检查
  • 缺点 :对内存不友好,如果一个key已经过期,但是一直没有使用,那么该key就会一直存在内存中,内存永远不会释放

定期删除

redis中的一个定时任务(100ms)执行一次,扫描设置了过期时间的键并判断是否过期。

具体细节:

redis并不会一次性扫描所有的设置过期时间的键,因为这样会浪费大量的过cpu资源,它会每次扫描时限制扫扫秒时间和数量,以免性能过大对redis正常的使用产生影响。

默认的话,每次获取20个key判断是否过期,如果过期的key占比超过25%,则继续拉20个,如果小于25%则停止。还有一次删除时间不能超过25ms,如果发现占比超过25%,就要判断目前是否花费了25ms,如果到时间也会结束。

定期清理有两种模式:

  • SLOW模式是定时任务,执行频率默认为10hz,每次不超过25ms,以通过修改配置文件redis.conf 的hz 选项来调整这个次数
  • FAST模式执行频率不固定,但两次间隔不低于2ms,每次耗时不超过1ms。

优缺点:

  • 优点:能有效释放过期键占用的内存,可以通过限制删除操作执行的时长和频率来减少删除操作对 CPU 的影响。
  • 缺点:难以确定删除操作执行的时长和频率。如果执行的太频繁,就会对 CPU 不友好;如果执行的太少,那又和惰性删除一样了,过期 key 占用的内存不会及时得到释放。

可以看到上面两种各自的优点,所以Redis使用惰性删除和定期删除两种策略相互使用,以求在合理的使用cpu和避免使用内存浪费之前取平衡。

10.Redis 的脑裂问题

复制代码
Redis脑裂(Split-Brain)是指在主从集群中,由于网络分区导致出现多个主节点同时接受写请求的情况,造成数据不一致的严重问题。

典型场景:主节点与部分从节点和哨兵网络断开,认为主节点客观下线,哨兵集群选举出新主节点,原主节点未真正下线,继续接受写请求,网络恢复后出现两个"主节点"。

脑裂问题的危害

数据不一致:两个主节点同时接受不同客户端的写入,相同key在不同节点有不同值

数据丢失:网络恢复后,旧主节点会被降级为从节点,其上的新写入数据会被清空(重新同步新主节点数据)

系统混乱:客户端可能连接到不同主节点

脑裂问题的主要原因

网络分区:主节点与哨兵/从节点间网络中断,但主节点与部分客户端连接仍保持

哨兵配置不当:quorum值设置过小,down-after-milliseconds时间过短

缺乏防护机制:未设置min-slaves参数,客户端未实现写失败处理

解决方案

  1. Redis服务端配置
    关键参数配置:
shell 复制代码
主节点必须有至少1个从节点才能写入
min-slaves-to-write 1
从节点延迟不超过10秒
min-slaves-max-lag 10
哨兵至少需要2个节点认为主节点不可用
sentinel monitor mymaster 127.0.0.1 6379 2
主节点失联30秒后才触发故障转移
sentinel down-after-milliseconds mymaster 30000
  1. 架构设计优化
    多机房部署:哨兵和从节点分布在不同的物理机房,避免单机房故障导致误判

网络冗余:主节点与哨兵间多条网络路径,使用心跳检测+冗余网络

更多更新的知识:Redis 面试题

如果有用请点赞收藏关注。

相关推荐
宇钶宇夕44 分钟前
EPLAN 电气制图:建立自己的部件库,添加部件-加SQL Server安装教程(三)上
运维·服务器·数据库·程序人生·自动化
爱可生开源社区1 小时前
SQLShift 重磅更新:支持 SQL Server 存储过程转换至 GaussDB!
数据库
贾修行2 小时前
SQL Server 空间函数从入门到精通:原理、实战与多数据库性能对比
数据库·sqlserver
傲祥Ax2 小时前
Redis总结
数据库·redis·redis重点总结
一屉大大大花卷3 小时前
初识Neo4j之入门介绍(一)
数据库·neo4j
周胡杰3 小时前
鸿蒙arkts使用关系型数据库,使用DB Browser for SQLite连接和查看数据库数据?使用TaskPool进行频繁数据库操作
前端·数据库·华为·harmonyos·鸿蒙·鸿蒙系统
wkj0013 小时前
navicate如何设置数据库引擎
数据库·mysql
赵渝强老师3 小时前
【赵渝强老师】Oracle RMAN的目录数据库
数据库·oracle
暖暖木头3 小时前
Oracle注释详解
数据库·oracle
御控工业物联网3 小时前
御控网关如何实现MQTT、MODBUS、OPCUA、SQL、HTTP之间协议转换
数据库·sql·http