【最全四种方案对比】Redis 与 MySQL 数据一致性问题探讨

news2024/11/25 20:54:50

前言:

  1. 缓存必须要有过期时间;
  2. 保证数据库跟缓存的最终一致性即可,不必追求强一致性。

目录如下:

  • 1. 什么是数据库与缓存一致性
  • 2. 缓存的使用策略
    • 2.1 Cache-Aside (旁路缓存)
    • 2.2 Read-Through(直读)
    • 2.3 Write-Through 同步直写
    • 2.4 Write-Behind
  • 3. 旁路缓存下的一致性问题分析
    • 3.1 先更新缓存,再更新数据库
    • 3.2 先更新数据库,再更新缓存
    • 3.3 先删缓存,再更新数据库
    • 3.4 先更新数据库,再删缓存
  • 4. 一致性解决方案有哪些?
    • 4.1 缓存延时双删
    • 4.2 删除缓存重试机制
    • 4.3 读取 binlog 异步删除
  • 总结

1. 什么是数据库与缓存一致性

数据一致性指的是:

  • 缓存中存有数据,缓存的数据值 = 数据库中的值;
  • 缓存中没有该数据,数据库中的值 = 最新值。

反推缓存与数据库不一致:

  • 缓存的数据值 ≠ 数据库中的值;
  • 缓存或者数据库存在旧的数据,导致线程读取到旧数据。

为何会出现数据一致性问题呢?

把 Redis 作为缓存的时候,当数据发生改变我们需要双写来保证缓存与数据库的数据一致。

数据库跟缓存,毕竟是两套系统,如果要保证强一致性,势必要引入 2PC 或 Paxos 等分布式一致性协议,或者分布式锁等等,这个在实现上是有难度的,而且一定会对性能有影响。

如果真的对数据的一致性要求这么高,那引入缓存是否真的有必要呢?

2. 缓存的使用策略

在使用缓存时,通常有以下几种缓存使用策略用于提升系统性能:

  • Cache-Aside Pattern(旁路缓存,业务系统常用)
  • Read-Through Pattern
  • Write-Through Pattern
  • Write-Behind Pattern

2.1 Cache-Aside (旁路缓存)

所谓「旁路缓存」,就是读取缓存、读取数据库和更新缓存的操作都在应用系统来完成业务系统最常用的缓存策略

2.1.1 读取数据

读取数据逻辑如下:

  1. 当应用程序需要从数据库读取数据时,先检查缓存数据是否命中。
  2. 如果缓存未命中,则查询数据库获取数据,同时将数据写到缓存中,以便后续读取相同数据会命中缓存,最后再把数据返回给调用者。
  3. 如果缓存命中,直接返回。

时序图如下:

旁路缓存读时序图

优点
  • 缓存中仅包含应用程序实际请求的数据,有助于保持缓存大小的成本效益。
  • 实现简单,并且能获得性能提升。

实现的伪代码如下:

String cacheKey = "小明";
String cacheValue = redisCache.get(cacheKey);
//缓存命中
if (cacheValue != null) {
  return cacheValue;
} else {
  //缓存缺失, 从数据库获取数据
  cacheValue = getDataFromDB();
  // 将数据写到缓存中
  redisCache.put(cacheValue)
}
缺点

由于数据仅在缓存未命中后才加载到缓存中,因此初次调用的数据请求响应时间会增加一些开销,因为需要额外的缓存填充和数据库查询耗时。

2.1.2 更新数据

使用 cache-aside 模式写数据时,如下流程。

旁路缓存写数据

  1. 写数据到数据库;
  2. 将缓存中的数据失效或者更新缓存数据;

使用 cache-aside 时,最常见的写入策略是直接将数据写入数据库,但是缓存可能会与数据库不一致。

我们应该给缓存设置一个过期时间,这个是保证最终一致性的解决方案。

如果过期时间太短,应用程序会不断地从数据库中查询数据。同样,如果过期时间过长,并且更新时没有使缓存失效,缓存的数据很可能是脏数据。

最常用的方式是删除缓存使缓存数据失效

为啥不是更新缓存呢?

性能问题

当缓存的更新成本很高,需要访问多张表联合计算,建议直接删除缓存,而不是更新缓存数据来保证一致性。

安全问题

在高并发场景下,可能会造成查询查到的数据是旧值,具体往下看

2.2 Read-Through(直读)

当缓存未命中,也就是从数据库加载数据,同时写到缓存中并返回给应用系统。

虽然 read-through 和 cache-aside 非常相似,在 cache-aside 中应用系统负责从数据库获取数据和填充缓存。

而 Read-Through 将获取数据存储中的值的责任转移到了缓存提供者身上。

Read-Through

Read-Through 实现了关注点分离原则。代码只与缓存交互,由缓存组件来管理自身与数据库之间的数据同步。

2.3 Write-Through 同步直写

与 Read-Through 类似,发生写请求时,Write-Through 将写入责任转移到缓存系统,由缓存抽象层来完成缓存数据和数据库数据的更新,时序流程图如下:

Write-Through

Write-Through 的主要好处是应用系统的不需要考虑故障处理和重试逻辑,交给缓存抽象层来管理实现。

优缺点

单独直接使用该策略是没啥意义的,因为该策略要先写缓存,再写数据库,对写入操作带来了额外延迟。

当Write-Through 与 Read-Through 配合使用,就能成分发挥 Read-Through 的优势,同时还能保证数据一致性,不需要考虑如何将缓存设置失效。

Write-Through

这个策略颠倒了 Cache-Aside 填充缓存的顺序,并不是在缓存未命中后延迟加载到缓存,而是在数据先写缓存,接着由缓存组件将数据写到数据库

优点
  • 缓存与数据库数据总是最新的;
  • 查询性能最佳,因为要查询的数据有可能已经被写到缓存中了。
缺点

不经常请求的数据也会写入缓存,从而导致缓存更大、成本更高。

2.4 Write-Behind

这个图一眼看去似乎与 Write-Through 一样,其实不是的,区别在于最后一个箭头的箭头:它从实心变为线。

这意味着缓存系统将异步更新数据库数据,应用系统只与缓存系统交互

应用程序不必等待数据库更新完成,从而提高应用程序性能,因为对数据库的更新是最慢的操作。

Write-Behind

这种策略下,缓存与数据库的一致性不强,对一致性高的系统不建议使用。

3. 旁路缓存下的一致性问题分析

业务场景用的最多的就是 Cache-Aside (旁路缓存) 策略,在该策略下,客户端对数据的读取流程是先读取缓存,如果命中则返回;未命中,则从数据库读取并把数据写到缓存中,所以读操作不会导致缓存与数据库的不一致。

重点是写操作,数据库和缓存都需要修改,而两者就会存在一个先后顺序,可能会导致数据不再一致。针对写,我们需要考虑两个问题:

  • 先更新缓存还是更新数据库?
  • 当数据发生变化时,选择修改缓存(update),还是删除缓存(delete)?

将这两个问题排列组合,会出现四种方案:

  1. 先更新缓存,再更新数据库;
  2. 先更新数据库,再更新缓存;
  3. 先删除缓存,再更新数据库;
  4. 先更新数据库,再删除缓存。

接下来的分析大家不必死记硬背,关键在于在推演的过程中大家只需要考虑以下两个场景会不会带来严重问题即可:

  • 其中第一个操作成功,第二个失败会导致什么问题?
  • 在高并发情况下会不会造成读取数据不一致?

为啥不考虑第一个失败,第二个成功的情况呀?

你猜?

既然第一个都失败了,第二个就不用执行了,直接在第一步返回 50x 等异常信息即可,不会出现不一致问题。

只有第一个成功,第二个失败才让人头痛,想要保证他们的原子性,就涉及到分布式事务的范畴了。

3.1 先更新缓存,再更新数据库

如果先更新缓存成功,写数据库失败,就会导致缓存是最新数据,数据库是旧数据,那缓存就是脏数据了。

之后,其他查询立马请求进来的时候就会获取这个数据,而这个数据数据库中却不存在。

数据库都不存在的数据,缓存并返回客户端就毫无意义了。

该方案直接 Pass

3.2 先更新数据库,再更新缓存

一切正常的情况如下:

  • 先写数据库,成功;
  • 再 update 缓存,成功。
更新缓存失败

这时候我们来推断下,假如这两个操作的原子性被破坏:第一步成功,第二步失败会导致什么问题?

会导致数据库是最新数据,缓存是旧数据,出现一致性问题。

高并发场景

谢霸歌经常 996,腰酸脖子疼,bug 越写越多,想去按摩推拿放提升下编程技巧。

疫情影响,单子来之不易,高端会所的技师都争先恐后想接这一单,高并发啊兄弟们。

在进店以后,前台会将顾客信息录入系统,执行 set xx的服务技师 = 待定的初始值表示目前无人接待保存到数据库和缓存中,之后再安排技师按摩服务。

如下图所示:

高并发先更新数据库,再更新缓存

  1. 98 号技师先下手为强,向系统发送 set 谢霸歌的服务技师 = 98 的指令写入数据库,这时候系统的网络出现波动,卡顿了,数据还没来得及写到缓存
  2. 接下来,520 号技师也向系统发送 set 谢霸哥的服务技师 = 520写到数据库中,并且也把这个数据写到缓存中了。
  3. 这时候之前的 98 号技师的写缓存请求开始执行,顺利将数据 set 谢霸歌的服务技师 = 98 写到缓存中。

最后发现,数据库的值 = set 谢霸哥的服务技师 = 520,而缓存的值= set 谢霸歌的服务技师 = 98。

520 号技师在缓存中的最新数据被 98 号技师的旧数据覆盖了。

所以,在高并发的场景中,多线程同时写数据再写缓存,就会出现缓存是旧值,数据库是最新值的不一致情况。

该方案直接 pass。

如果第一步就失败,直接返回 50x 异常,并不会出现数据不一致。

3.3 先删缓存,再更新数据库

按照前面说的套路,假设第一个操作成功,第二个操作失败推断下会发生什么?高并发场景下又会发生什么?

第二步写数据库失败

假设现在有两个请求:写请求 A,读请求 B。

写请求 A 第一步先删除缓存成功,写数据到数据库失败,就会导致该次写数据丢失,数据库保存的是旧值

接着另一个读请 B 求进来,发现缓存不存在,从数据库读取旧数据并写到缓存中。

高并发下的问题

先删缓存,再写数据库

  1. 还是 98 号技师先下手为强,系统接收请求把缓存数据删除,当系统准备将 set 肖菜鸡的服务技师 = 98写到数据库的时候发生卡顿,来不及写入。
  2. 这时候,大堂经理向系统执行读请求,查下肖菜鸡有没有技师接待,方便安排技师服务,系统发现缓存中没数据,于是乎就从数据库读取到旧数据 set 肖菜鸡的服务技师 = 待定,并写到缓存中。
  3. 这时候,原先卡顿的 98 号技师写数据 set 肖菜鸡的服务技师 = 98到数据库的操作完成。

这样子会出现缓存的是旧数据,在缓存过期之前无法读取到最数据。肖菜鸡本就被 98 号技师接单了,但是大堂经理却以为没人接待。

该方案 pass,因为第一步成功,第二步失败,会造成数据库是旧数据,缓存中没数据继续从数据库读取旧值写入缓存,造成数据不一致,还会多一次 cahche。

不论是异常情况还是高并发场景,会导致数据不一致。miss。

3.4 先更新数据库,再删缓存

经过前面的三个方案,全都被 pass 了,分析下最后的方案到底行不行。

按照「套路」,分别判断异常和高并发会造成什么问题。

该策略可以知道,在写数据库阶段失败的话就直返返回客户端异常,不需要执行缓存操作了。

所以第一步失败不会出现数据不一致的情况。

删缓存失败

重点在于第一步写最新数据到数据库成功,删除缓存失败怎么办?

可以把这两个操作放在一个事务中,当缓存删除失败,那就把写数据库回滚。

高并发场景下不合适,容易出现大事务,造成死锁问题。

如果不回滚,那就出现数据库是新数据,缓存还是旧数据,数据不一致了,咋办?

所以,我们要想办法让缓存删除成功,不然只能等到有效期失效那可不行。

使用重试机制。

比如重试三次,三次都失败则记录日志到数据库,使用分布式调度组件 xxl-job 等实现后续的处理。

在高并发的场景下,重试最好使用异步方式,比如发送消息到 mq 中间件,实现异步解耦。

亦或是利用 Canal 框架订阅 MySQL binlog 日志,监听对应的更新请求,执行删除对应缓存操作。

高并发场景

再来分析下高并发读写会有什么问题……

先写数据库后删缓存

  1. 98 号技师先下手为强,接下肖菜鸡的这笔生意,数据库执行 set 肖菜鸡的服务技师 = 98;还是网络卡顿了下,没来得及执行删除缓存操作
  2. 主管 Candy 向系统执行读请求,查下肖菜鸡有没有技师接待,发现缓存中有数据 肖菜鸡的服务技师 = 待定,直接返回信息给客户端,主管以为没人接待。
  3. 原先 98 号技师接单,由于卡顿没删除缓存的操作现在执行删除成功。

读请求可能出现少量读取旧数据的情况,但是很快旧数据就会被删除,之后的请求都能获取最新数据,问题不大。

还有一种比较极端的情况,缓存自动失效的时候又遇到了高并发读写的情况,假设这会有两个请求,一个线程 A 做查询操作,一个线程 B 做更新操作,那么会有如下情形产生:

缓存忽然失效

  1. 缓存的过期时间到期,缓存失效。
  2. 线程 A 读请求读取缓存,没命中,则查询数据库得到一个旧的值(因为 B 会写新值,相对而言就是旧的值了),准备把数据写到缓存时发送网络问题卡顿了
  3. 线程 B 执行写操作,将新值写数据库。
  4. 线程 B 执行删除缓存。
  5. 线程 A 继续,从卡顿中醒来,把查询到的旧值写到入缓存。

这咋玩,还是出现了不一致的情况啊。

不要慌,发生这个情况的概率微乎其微,发生上述情况的必要条件是:

  1. 步骤 (3)的写数据库操作要比步骤(2)读操作耗时短速度快,才可能使得步骤(4)先于步骤(5)。
  2. 缓存刚好到达过期时限。

通常 MySQL 单机的 QPS 大概 5K 左右,而 TPS 大概 1k 左右,(ps:Tomcat 的 QPS 4K 左右,TPS = 1k 左右)。

数据库读操作是远快于写操作的(正是因为如此,才做读写分离),所以步骤(3)要比步骤(2)更快这个情景很难出现,同时还要配合缓存刚好失效。

所以,在用旁路缓存策略的时候,对于写操作推荐使用:先更新数据库,再删除缓存。

4. 一致性解决方案有哪些?

最后,针对 Cache-Aside (旁路缓存) 策略,写操作使用先更新数据库,再删除缓存的情况下,我们来分析下数据一致性解决方案都有哪些?

4.1 缓存延时双删

如果采用先删除缓存,再更新数据库如何避免出现脏数据?

采用延时双删策略。

  1. 先删除缓存。
  2. 写数据库。
  3. 休眠 500 毫秒,再删除缓存。

这样子最多只会出现 500 毫秒的脏数据读取时间。关键是这个休眠时间怎么确定呢?

延迟时间的目的就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。

所以我们需要自行评估项目的读数据业务逻辑的耗时,在读耗时的基础上加几百毫秒作为延迟时间即可

4.2 删除缓存重试机制

缓存删除失败怎么办?比如延迟双删的第二次删除失败,那岂不是无法删除脏数据。

使用重试机制,保证删除缓存成功。

比如重试三次,三次都失败则记录日志到数据库并发送警告让人工介入。

在高并发的场景下,重试最好使用异步方式,比如发送消息到 mq 中间件,实现异步解耦。

重试机制

第(5)步如果删除失败且未达到重试最大次数则将消息重新入队,直到删除成功,否则就记录到数据库,人工介入。

该方案有个缺点,就是对业务代码中造成侵入,于是就有了下一个方案,启动一个专门订阅 数据库 binlog 的服务读取需要删除的数据进行缓存删除操作。

4.3 读取 binlog 异步删除

binlog异步删除

  1. 更新数据库;
  2. 数据库会把操作信息记录在 binlog 日志中;
  3. 使用 canal 订阅 binlog 日志获取目标数据和 key;
  4. 缓存删除系统获取 canal 的数据,解析目标 key,尝试删除缓存。
  5. 如果删除失败则将消息发送到消息队列;
  6. 缓存删除系统重新从消息队列获取数据,再次执行删除操作。

总结

缓存策略的最佳实践是 Cache Aside Pattern。分别分为读缓存最佳实践和写缓存最佳实践。

读缓存最佳实践:先读缓存,命中则返回;未命中则查询数据库,再写到缓存中。

写缓存最佳实践:

  • 先写数据库,再操作缓存;
  • 直接删除缓存,而不是修改,因为当缓存的更新成本很高,需要访问多张表联合计算,建议直接删除缓存,而不是更新,另外,删除缓存操作简单,副作用只是增加了一次 chache miss,建议大家使用该策略。

在以上最佳实践下,为了尽可能保证缓存与数据库的一致性,我们可以采用延迟双删。

防止删除失败,我们采用异步重试机制保证能正确删除,异步机制我们可以发送删除消息到 mq 消息中间件,或者利用 canal 订阅 MySQL binlog 日志监听写请求删除对应缓存。

那么,如果我非要保证绝对一致性怎么办,先给出结论:

没有办法做到绝对的一致性,这是由 CAP 理论决定的,缓存系统适用的场景就是非强一致性的场景,所以它属于 CAP 中的 AP。

所以,我们得委曲求全,可以去做到 BASE 理论中说的最终一致性

其实一旦在方案中使用了缓存,那往往也就意味着我们放弃了数据的强一致性,但这也意味着我们的系统在性能上能够得到一些提升。

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

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

相关文章

秒杀优化-异步秒杀思路

6、秒杀优化 6.1 秒杀优化-异步秒杀思路 我们来回顾一下下单流程 当用户发起请求,此时会请求nginx,nginx会访问到tomcat,而tomcat中的程序,会进行串行操作,分成如下几个步骤 1、查询优惠卷 2、判断秒杀库存是否足…

vue 原理【详解】MVVM、响应式、模板编译、虚拟节点 vDom、diff 算法

vue 的设计模式 —— MVVM M —— Model 模型,即数据V —— View 视图,即DOM渲染VM —— ViewModel 视图模型,用于实现Model和View的通信,即数据改变驱动视图渲染,监听视图事件修改数据 初次渲染 将模板编译为 render …

面试-数据库基础以及MySql、ClickHost、Redis简介

面试-数据库基础以及MySql、ClickHost、Redis简介 0.数据完整性1.数据库并发控制1.1事物1.2 并发读写错误1.3 锁1.3.1 乐观锁与悲观锁1.3.2 共享锁和排他锁1.3.3 行锁与表锁1.3.4 意向锁 1.4 封锁协议与隔离级别1.5 MVCC1.5.1 概念1.5.2 当前读与快照读1.5.3 MVCC in InnoDB 2.…

linux启动流程(s3c2400)

概述 大致流程:内核(kernel)都是由bootloader程序引导启动的,所以我们应该先烧进去bootloader程序。然后可以通过保存的内核代码或者通过远程连接(nfs/tftp)的主机下载再运行,再挂载根文件系统。…

海外媒体宣发:旅游业媒体5个提高转化率绝佳实践方法-华媒舍

随着旅游业的迅速发展和数字化进程的加速,旅游业媒体成为了推动旅游业发展的重要力量。仅仅依靠大量流量和曝光并不足以实现收益最大化,更为关键的是如何提高旅游业媒体的转化率,将流量转化为实际销售和盈利。本篇文章将介绍并讨论5个提高旅游…

『VUE』16. v-model表单输入和绑定(详细图文注释)

目录 绑定机制v-model修饰符简易绑定使用修饰符lazy实现勾选框效果总结 欢迎关注 『VUE』 专栏,持续更新中 欢迎关注 『VUE』 专栏,持续更新中 绑定机制 在 Vue.js 中,使用 v-model 指令可以实现表单输入元素与 Vue 实例中的数据双向绑定。这…

Redis--16--Spring Data Redis

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 Spring Data Redishttps://spring.io/projects/spring-data-redis 1.依赖2.RedisTemplate3.案例 序列化1.默认是 JdkSerializationRedisSerializer2.添加Redis配置文…

CSS导读 (复合选择器 上)

(大家好,今天我们将继续来学习CSS的相关知识,大家可以在评论区进行互动答疑哦~加油!💕) 目录 二、CSS的复合选择器 2.1 什么是复合选择器 2.2 后代选择器(重要) 2.3 子选择器(重要) Questions 小提…

GRE/MGRE详解

GRE GRE:通用路由封装,是标准的三层隧道技术,是一种点对点的隧道技术; 该技术可以实现不同的网络之间安全的访问; 如上:可以使用该技术搭建一条专线,实现公司A与分公司A1之间相互通信&#xf…

蓝桥杯 前一晚总结 模板 新手版

《准备实足&#xff0c;冲冲冲 省一》https://www.yuque.com/lenyan-svokd/hi7hp2/hfka297matrtsxy2?singleDoc# 《准备实足&#xff0c;冲冲冲 省一》 #include<bits/stdc.h> // 包含标准库头文件using namespace std; using ll long long; // 定义 long long 数据类…

00_如何使用国内镜像源下载QT

如何使用国内镜像源下载QT 如何使用国内镜像源下载QT 如何使用国内镜像源下载QT 第一步&#xff1a;下载下载qt online installer 网站&#xff1a;https://download.qt.io/official_releases/online_installers/ 添加链接描述 下载windows版本 第二步&#xff1a; 剪切放…

synchronized的优化策略

synchronized的优化策略 一:synchronized 的"自适应"1.1:偏向锁 二:锁消除三:锁粗化 一:synchronized 的"自适应" 锁升级的过程: (1)未加锁的状态(无锁) 当代码中开始调用执行synchronized (2)偏向锁 遇到锁冲突 (3)轻量级锁 冲突进一步提升 (4)重量级锁 …

streamlit 大模型前段界面

结合 langchain 一起使用的工具&#xff0c;可以显示 web 界面 pip install streamlit duckduckgo-search 运行命令 streamlit run D:\Python_project\NLP\大模型学习\test.py import os from dotenv import load_dotenv from langchain_community.llms import Tongyi load…

主从同步优化

2.3.主从同步优化 主从同步可以保证主从数据的一致性&#xff0c;非常重要。 可以从以下几个方面来优化Redis主从就集群&#xff1a; 在master中配置repl-diskless-sync yes启用无磁盘复制&#xff0c;避免全量同步时的磁盘IO。Redis单节点上的内存占用不要太大&#xff0c;…

深度强化学习(DRL)算法 附录 6 —— NLP 回顾之基础模型篇

NLP 的序列属性和 RL 天然适配&#xff0c;所以 NLP 里的一些模型也可以用到 RL 里面&#xff0c;如 Transformer。去年发表的 MATransformer 在一些多智能体任务上超过了 MAPPO&#xff0c;可见 Transformer 在 RL 上有巨大的发展潜力。这篇文章用来回顾 NLP 基础模型。 文本…

企业航拍VR全景视频展示仿如上门参观

360度VR全景视频因其广阔的视野和身临其境的体验&#xff0c;无论再房产楼盘的精致呈现&#xff0c;旅游景点的全景漫游&#xff0c;还是校园风光的生动展示&#xff0c;都成为企业商户的首选。 360度vr全景视频编辑软件是深圳VR公司华锐视点提供多种常见的三维仿真场景供选择&…

酷开科技OTT大屏营销:开启新时代的营销革命

随着互联网技术的不断发展和普及&#xff0c;大屏已经成为越来越多家庭选择的娱乐方式。在这个背景下&#xff0c;酷开科技凭借其强大的技术实力和敏锐的市场洞察力&#xff0c;成功地将大屏转化为一种新的营销渠道&#xff0c;为品牌和企业带来了前所未有的商业机会。 酷开科技…

Vue pdfjs

最终效果图 官网 https://mozilla.github.io/pdf.js 下载 放入项目 vue页面嵌入本地下载好的html sessionStorage.setItem(sdfDldj8KJ45SDF, encodeURIComponent(file_url)) <template><div style"height:100%"><iframe:id"1":key"…

C/C++内存泄漏及检测

“该死系统存在内存泄漏问题”&#xff0c;项目中由于各方面因素&#xff0c;总是有人抱怨存在内存泄漏&#xff0c;系统长时间运行之后&#xff0c;可用内存越来越少&#xff0c;甚至导致了某些服务失败。内存泄漏是最难发现的常见错误之一&#xff0c;因为除非用完内存或调用…

python-study-day2

pycharm注释(也可修改) 快捷键ctrl /手敲一个 " # " 这个是单行注释""" """ 左边这个三个引号可以完成多行注释 基础知识 常用的数据类型 def hello(self):print("Hello")print(type(1)) print(type("1"…