浅析数据库缓存一致性问题

在真实的业务场景中,我们的业务的数据——例如订单、会员、支付等——都是持久化到数据库中的,因为数据库能有很好的事务保证、持久化保证。但是,正因为数据库要能够满足这么多优秀的功能特性,使得数据库在设计上通常难以兼顾到性能,因此往往不能满足大型流量下的性能要求,像是 MySQL 数据库只能承担“千”这个级别的 QPS,否则很可能会不稳定,进而导致整个系统的故障。

但是客观上,我们的业务规模很可能要求着更高的 QPS,有些业务的规模本身就非常大,也有些业务会遇到一些流量高峰,比如电商会遇到大促的情况。

而这时候大部分的流量实际上都是读请求,而且大部分数据也是没有那么多变化的,如热门商品信息、微博的内容等常见数据就是如此。此时,缓存就是我们应对此类场景的利器。

01

缓存的意义

所谓缓存,实际上就是用空间换时间,准确地说是用更高速的空间来换时间从而整体上提升读的性能。

何为更高速的空间呢?

更快的存储介质。通常情况下,如果说数据库的速度慢,就得用更快的存储组件去替代它,目前最常见的就是 Redis(内存存储)。Redis 单实例的读 QPS 可以高达 10w/s,90% 的场景下只需要正确使用 Redis 就能应对。

就近使用本地内存。就像 CPU 也有高速缓存一样,缓存也可以分为一级缓存、二级缓存。即便 Redis 本身性能已经足够高了,但访问一次 Redis 毕竟也需要一次网络 IO,而使用本地内存无疑有更快的速度。不过单机的内存是十分有限的,所以这种一级缓存只能存储非常少量的数据,通常是最热点的那些 key 对应的数据。这就相当于额外消耗宝贵的服务内存去换取高速的读取性能。

02

引入缓存后的一致性挑战

用空间换时间,意味着数据同时存在于多个空间。最常见的场景就是数据同时存在于 Redis 与 MySQL 上(为了问题的普适性,后面举例中若没有特别说明,缓存均指 Redis 缓存)。

实际上,最权威最全的数据还是在 MySQL 里的。而万一 Redis 数据没有得到及时的更新(例如数据库更新了没更新到 Redis),就出现了数据不一致。

大部分情况下,只要使用了缓存,就必然会有不一致的情况出现,只是说这个不一致的时间窗口是否能做到足够的小。有些不合理的设计可能会导致数据持续不一致,这是我们需要改善设计去避免的。

这里的一致性实际上对于本地缓存也是同理的,例如数据库更新后没有及时更新本地缓存,也是有一致性问题的,下文统一以 Redis 缓存作为引子讲述,实际上处理本地缓存原理基本一致。

缓存不一致性无法客观地完全消灭

为什么我们几乎没办法做到缓存和数据库之间的强一致呢?

理想情况下,我们需要在数据库更新完后把对应的最新数据同步到缓存中,以便在读请求的时候能读到新的数据而不是旧的数据(脏数据)。但是很可惜,由于数据库和 Redis 之间是没有事务保证的,所以我们无法确保写入数据库成功后,写入 Redis 也是一定成功的;即便 Redis 写入能成功,在数据库写入成功后到 Redis 写入成功前的这段时间里,Redis 数据也肯定是和 MySQL 不一致的。如下两图所示:

图片

无法事务保持一致

所以说这个时间窗口是没办法完全消灭的,除非我们付出极大的代价,使用分布式事务等各种手段去维持强一致,但是这样会使得系统的整体性能大幅度下降,甚至比不用缓存还慢,这样不就与我们使用缓存的目标背道而驰了吗?

不过虽然无法做到强一致,但是我们能做到的是缓存与数据库达到最终一致,而且不一致的时间窗口我们能做到尽可能短,按照经验来说,如果能将时间优化到 1ms 之内,这个一致性问题带来的影响我们就可以忽略不计。

图片

03

更新缓存的手段

通常情况下,我们在处理查询请求的时候,使用缓存的逻辑如下:

data = queryDataRedis(key);if (data ==null) {     data = queryDataMySQL(key); //缓存查询不到,从MySQL做查询     if (data!=null) {         updateRedis(key, data);//查询完数据后更新MySQL最新数据到Redis     }}

也就是说优先查询缓存,查询不到才查询数据库。如果这时候数据库查到数据了,就将缓存的数据进行更新。这是我们常说的 cache aside 的策略,也是最常用的策略。

这样的逻辑是正确的,而一致性的问题一般不来源于此,而是出现在处理写请求的时候。所以我们简化成最简单的写请求的逻辑,此时你可能会面临多个选择,究竟是直接更新缓存,还是失效缓存?而无论是更新缓存还是失效缓存,都可以选择在更新数据库之前,还是之后操作。

这样就演变出 4 个策略:更新数据库后更新缓存、更新数据库前更新缓存、更新数据库后删除缓存、更新数据库前删除缓存。下面我们来分别讲述。

更新数据库后更新缓存的不一致问题

一种常见的操作是,设置一个过期时间,让写请求以数据库为准,过期后,读请求同步数据库中的最新数据给缓存。那么在加入了过期时间后,是否就不会有问题了呢?不是这样。

大家设想一下这样的场景。

假如这里有一个计数器,把数据库自减 1,原始数据库数据是 100,同时有两个写请求申请计数减一,假设线程 A 先减数据库成功,线程 B 后减数据库成功。那么这时候数据库的值是 98,缓存里正确的值应该也要是 98。

但是特殊场景下,你可能会遇到这样的情况:

  • 线程 A 和线程 B 同时更新这个数据。

  • 更新数据库的顺序是先 A 后 B。

  • 更新缓存时顺序是先 B 后 A。

如果我们的代码逻辑还是更新数据库后立刻更新缓存的数据,那么——

updateMySQL();updateRedis(key, data);

就可能出现:数据库的值是 100->99->98,但是缓存的数据却是 100->98->99,也就是数据库与缓存的不一致。而且这个不一致只能等到下一次数据库更新或者缓存失效才可能修复。

时间线程A(写请求)线程B(写请求)问题
T1更新数据库为99
T2更新数据库为98
T3更新缓存数据为98
T4更新缓存数据为99此时缓存的值被显式更新为99,但是实际上数据库的值已经是98,数据不一致

当然,如果更新 Redis 本身是失败的话,两边的值固然也是不一致的,这个前文也阐述过,几乎无法根除。

图片

更新数据库前更新缓存的不一致问题

那你可能会想,这是否表示,我应该先让缓存更新,之后再去更新数据库呢?类似这样:

updateRedis(key, data);//先更新缓存updateMySQL();//再更新数据库

这样操作产生的问题更是显而易见的,因为我们无法保证数据库的更新成功,万一数据库更新失败了,你缓存的数据就不只是脏数据,而是错误数据了。

图片

你可能会想,是否我在更新数据库失败的时候做 Redis 回滚的操作能够解决呢?这其实也是不靠谱的,因为我们也不能保证这个回滚的操作 100% 被成功执行。

同时,在写写并发的场景下,同样有类似的一致性问题,请看以下情况:

  • 线程 A 和线程 B 同时更新同这个数据。

  • 更新缓存的顺序是先 A 后 B。

  • 更新数据库的顺序是先 B 后 A。

举个例子。线程 A 希望把计数器置为 0,线程 B 希望置为 1。而按照以上场景,缓存确实被设置为 1,但数据库却被设置为 0。

所以通常情况下,更新缓存再更新数据库是我们应该避免使用的一种手段。

更新数据库前删除缓存的问题

那如果采取删除缓存的策略呢?也就是说我们在更新数据库的时候失效对应的缓存,让缓存在下次触发读请求时进行更新,是否会更好呢?同样地,针对在更新数据库前和数据库后这两个删除时机,我们来比较下其差异。

最直观的做法,我们可能会先让缓存失效,然后去更新数据库,代码逻辑如下:

deleteRedis(key);//先删除缓存让缓存失效updateMySQL();//再更新数据库

这样的逻辑看似没有问题,毕竟删除缓存后即便数据库更新失败了,也只是缓存上没有数据而已。然后并发两个写请求过来,无论怎么样的执行顺序,缓存最后的值也都是会被删除的,也就是说在并发写写的请求下这样的处理是没问题的。

然而,这种处理在读写并发的场景下却存在着隐患。

还是刚刚更新计数的例子。例如现在缓存的数据是 100,数据库也是 100,这时候需要对此计数减 1,减成功后,数据库应该是 99。如果这之后触发读请求,缓存如果有效的话,里面应该也要被更新为 99 才是正确的。

那么思考下这样的请求情况:

  • 线程 A 更新这个数据的同时,线程 B 读取这个数据。

  • 线程 A 成功删除了缓存里的老数据,这时候线程 B 查询数据发现缓存失效。

  • 线程 A 更新数据库成功。

时间线程A(写请求)线程B(读请求)问题
T1删除缓存值
T21.读取缓存数据,缓存缺失,从数据库读取数据100
T3更新数据库中的数据X的值为99
T4将数据100的值写入缓存此时缓存的值被显式更新为100,但是实际上数据库的值已经是99了

可以看到,在读写并发的场景下,一样会有不一致的问题。

针对这种场景,有个做法是所谓的“延迟双删策略”,就是说,既然可能因为读请求把一个旧的值又写回去,那么我在写请求处理完之后,等到差不多的时间延迟再重新删除这个缓存值。

时间线程A(写请求)线程C(新的读请求)线程D(新的读请求)问题
T5sleep(N)缓存存在,读取到缓存旧值100其他线程可能在双删成功前读到脏数据
T6删除缓存值
T7缓存缺失,从数据库读取数据的最新值(99)

这种解决思路的关键在于对 N 的时间的判断,如果 N 时间太短,线程 A 第二次删除缓存的时间依旧早于线程 B 把脏数据写回缓存的时间,那么相当于做了无用功。而 N 如果设置得太长,那么在触发双删之前,新请求看到的都是脏数据。

更新数据库后删除缓存

那如果我们把更新数据库放在删除缓存之前呢,问题是否解决?我们继续从读写并发的场景看下去,有没有类似的问题。

时间线程A(写请求)线程B(读请求)线程C(读请求)潜在问题
T1更新主库 X = 99(原值 X = 100)
T2读取数据,查询到缓存还有数据,返回100线程C实际上读取到了和数据库不一致的数据
T3删除缓存
T4查询缓存,缓存缺失,查询数据库得到当前值99
T5将99写入缓存

可以看到,大体上,采取先更新数据库再删除缓存的策略是没有问题的,仅在更新数据库成功到缓存删除之间的时间差内——[T2,T3)的窗口 ,可能会被别的线程读取到老值。

而在开篇的时候我们说过,缓存不一致性的问题无法在客观上完全消灭,因为我们无法保证数据库和缓存的操作是一个事务里的,而我们能做到的只是尽量缩短不一致的时间窗口。

在更新数据库后删除缓存这个场景下,不一致窗口仅仅是 T2 到 T3 的时间,内网状态下通常不过 1ms,在大部分业务场景下我们都可以忽略不计。因为大部分情况下一个用户的请求很难能再 1ms 内快速发起第二次。

但是真实场景下,还是会有一个情况存在不一致的可能性,这个场景是读线程发现缓存不存在,于是读写并发时,读线程回写进去老值。并发情况如下:

时间线程A(写请求)线程B(读请求--缓存不存在场景)潜在问题
T1查询缓存,缓存缺失,查询数据库得到当前值100
T2更新主库 X = 99(原值 X = 100)
T3删除缓存
T4将100写入缓存此时缓存的值被显式更新为100,但是实际上数据库的值已经是99了

总的来说,这个不一致场景出现条件非常严格,因为并发量很大时,缓存不太可能不存在;如果并发很大,而缓存真的不存在,那么很可能是这时的写场景很多,因为写场景会删除缓存。

所以待会我们会提到,写场景很多时候实际上并不适合采取删除策略。

总结四种更新策略

终上所述,我们对比了四个更新缓存的手段,做一个总结对比,其中应对方案也提供参考,具体不做展开,如下表:

策略并发场景潜在问题应对方案
更新数据库+更新缓存写+读线程A未更新完缓存之前,线程B的读请求会短暂读到旧值可以忽略
写+写更新数据库的顺序是先A后B,但更新缓存时顺序是先B后A,数据库和缓存数据不一致分布式锁(操作重)
更新缓存+更新数据库无并发线程A还未更新完缓存但是更新数据库可能失败利用MQ确认数据库更新成功(较复杂)
写+写更新缓存的顺序是先A后B,但更新数据库时顺序是先B后A分布式锁(操作很重)
删除缓存值+更新数据库写+读写请求的线程A删除了缓存在更新数据库之前,这时候读请求线程B到来,因为缓存缺失,则把当前数据读取出来放到缓存,而后线程A更新成功了数据库延迟双删(但是延迟的时间不好估计,且延迟的过程中依旧有不一致的时间窗口)
更新数据库+删除缓存值写+读(缓存命中)线程A完成数据库更新成功后,尚未删除缓存,线程B有并发读请求会读到旧的脏数据可以忽略
写+读(缓存不命中)读请求不命中缓存,写请求处理完之后读请求才回写缓存,此时缓存不一致分布式锁(操作重)

从一致性的角度来看,采取更新数据库后删除缓存值,是更为适合的策略。因为出现不一致的场景的条件更为苛刻,概率相比其他方案更低。

那么是否更新缓存这个策略就一无是处呢?不是的!

删除缓存值意味着对应的 Key 会失效,那么这时候读请求都会打到数据库。如果这个数据的写操作非常频繁,就会导致缓存的作用变得非常小。而如果这时候某些 Key 还是非常大的热 Key,就可能因为扛不住数据量而导致系统不可用。

如下图所示:

图片

删除策略频繁的缓存失效导致读请求无法利用缓存

所以做个简单总结,足以适应绝大部分的互联网开发场景的决策:

  • 针对大部分读多写少场景,建议选择更新数据库后删除缓存的策略。

  • 针对读写相当或者写多读少的场景,建议选择更新数据库后更新缓存的策略。

04

最终一致性如何保证?

缓存设置过期时间

第一个方法便是我们上面提到的,当我们无法确定 MySQL 更新完成后,缓存的更新/删除一定能成功,例如 Redis 挂了导致写入失败了,或者当时网络出现故障,更常见的是服务当时刚好发生重启了,没有执行这一步的代码。

这些时候 MySQL 的数据就无法刷到 Redis 了。为了避免这种不一致性永久存在,使用缓存的时候,我们必须要给缓存设置一个过期时间,例如 1 分钟,这样即使出现了更新 Redis 失败的极端场景,不一致的时间窗口最多也只是 1 分钟。

这是我们最终一致性的兜底方案,万一出现任何情况的不一致问题,最后都能通过缓存失效后重新查询数据库,然后回写到缓存,来做到缓存与数据库的最终一致。

05

如何减少缓存删除/更新的失败?

万一删除缓存这一步因为服务重启没有执行,或者 Redis 临时不可用导致删除缓存失败了,就会有一个较长的时间(缓存的剩余过期时间)是数据不一致的。

那我们有没有什么手段来减少这种不一致的情况出现呢?这时候借助一个可靠的消息中间件就是一个不错的选择。

因为消息中间件有 ATLEAST-ONCE 的机制,如下图所示。

图片

我们把删除 Redis 的请求以消费 MQ 消息的手段去失效对应的 Key 值,如果 Redis 真的存在异常导致无法删除成功,我们依旧可以依靠 MQ 的重试机制来让最终 Redis 对应的 Key 失效。

而你们或许会问,极端场景下,是否存在更新数据库后 MQ 消息没发送成功,或者没机会发送出去机器就重启的情况?

这个场景的确比较麻烦,如果 MQ 使用的是 RocketMQ,我们可以借助 RocketMQ 的事务消息,来让删除缓存的消息最终一定发送出去。而如果你没有使用 RocketMQ,或者你使用的消息中间件并没有事务消息的特性,则可以采取消息表的方式让更新数据库和发送消息一起成功。事实上这个话题比较大了,我们不在这里展开。

06

如何处理复杂的多缓存场景?

有些时候,真实的缓存场景并不是数据库中的一个记录对应一个 Key 这么简单,有可能一个数据库记录的更新会牵扯到多个 Key 的更新。还有另外一个场景是,更新不同的数据库的记录时可能需要更新同一个 Key 值,这常见于一些 App 首页数据的缓存。

我们以一个数据库记录对应多个 Key 的场景来举例。

假如系统设计上我们缓存了一个粉丝的主页信息、主播打赏榜 TOP10 的粉丝、单日 TOP 100 的粉丝等多个信息。如果这个粉丝注销了,或者这个粉丝触发了打赏的行为,上面多个 Key 可能都需要更新。只是一个打赏的记录,你可能就要做:

updateMySQL();//更新数据库一条记录deleteRedisKey1();//失效主页信息的缓存updateRedisKey2();//更新打赏榜TOP10deleteRedisKey3();//更新单日打赏榜TOP100

这就涉及多个 Redis 的操作,每一步都可能失败,影响到后面的更新。甚至从系统设计上,更新数据库可能是单独的一个服务,而这几个不同的 Key 的缓存维护却在不同的 3 个微服务中,这就大大增加了系统的复杂度和提高了缓存操作失败的可能性。最可怕的是,操作更新记录的地方很大概率不只在一个业务逻辑中,而是散发在系统各个零散的位置。

针对这个场景,解决方案和上文提到的保证最终一致性的操作一样,就是把更新缓存的操作以 MQ 消息的方式发送出去,由不同的系统或者专门的一个系统进行订阅,而做聚合的操作。如下图:

图片

不同业务系统订阅 MQ 消息单独维护各自的缓存 Key

07

通过订阅 MySQL binlog 的方式处理缓存

上面讲到的 MQ 处理方式需要业务代码里面显式地发送 MQ 消息。还有一种优雅的方式便是订阅 MySQL 的 binlog,监听数据的真实变化情况以处理相关的缓存。

例如刚刚提到的例子中,如果粉丝又触发打赏了,这时候我们利用 binlog 表监听是能及时发现的,发现后就能集中处理了,而且无论是在什么系统什么位置去更新数据,都能做到集中处理。

目前业界类似的产品有 Canal,具体的操作图如下:

图片

利用 Canel 订阅数据库 binlog 变更从而发出 MQ 消息,让一个专门消费者服务维护所有相关 Key 的缓存操作

到这里,针对大型系统缓存设计如何保证最终一致性,我们已经从策略、场景、操作方案等角度进行了细致的讲述,希望能对你起到帮助。

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

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

相关文章

element plus el-form自定义验证输入框为纯数字函数

element plus 的el-form 使用自定义验证器&#xff0c;验证纯数字&#xff0c;禁止输入小数、中文、字母、特殊符号。input的maxlength为最大输入多少位长度 效果图 <el-form ref"dataFormRef" :model"dataForm" :rules"dataRules" label-w…

jira如何查看历史Sprint

方法一&#xff1a;通过看板模块查看历史 Sprint 进入看板模块 在项目的看板中&#xff0c;找到并点击“模块项”。在右侧历史记录中选择一个模块项。 查看 Sprint 历史 进入模块项界面后&#xff0c;点击“搜索”按钮旁边的“更多”下拉菜单。勾选“Sprint”选项&#xff0c;…

阿里云 DataWorks 正式支持 SelectDB Apache Doris 数据源,实现 MySQL 整库实时同步

SelectDB 是由飞轮科技基于 Apache Doris 内核打造的现代化数据仓库&#xff0c;支持大规模实时数据上的极速查询分析。 通过实时、统一、弹性、开放的核心能力&#xff0c;能够为企业提供高性价比、简单易用、安全稳定、低成本的实时大数据分析支持。SelectDB 具备世界领先的实…

OV(企业型)通配符域名SSL证书

SSL证书是由CA机构签发的&#xff0c;相信这一点大家都知道&#xff0c;然而目前全世界兼容性可以达到99%机构仅有&#xff1a;GlobalSign、DigiCert、Sectigo、Certum&#xff0c;最后一家还是勉强。 SSL证书选择OV&#xff08;国内有人称之为企业型&#xff09;其实就是实名…

软考系统分析师知识点三五: 考前强记知识点

前言 今年报考了11月份的软考高级&#xff1a;系统分析师。 考试时间&#xff1a;11月9日。 倒计时&#xff1a;2天。 目标&#xff1a;优先应试&#xff0c;其次学习&#xff0c;再次实践。 复习计划第四阶段&#xff1a;考前强记知识点。 考前强记知识点 系统分析主要任…

基础算法——排序算法(冒泡排序,选择排序,堆排序,插入排序,希尔排序,归并排序,快速排序,计数排序,桶排序,基数排序,Java排序)

1.概述 比较排序算法 算法最好最坏平均空间稳定思想注意事项冒泡O(n)O( n 2 n^2 n2)O( n 2 n^2 n2)O(1)Y比较最好情况需要额外判断选择O( n 2 n^2 n2)O( n 2 n^2 n2)O( n 2 n^2 n2)O(1)N比较交换次数一般少于冒泡堆O( n l o g n nlogn nlogn)O( n l o g n nlogn nlogn)O( n l…

探索 Python 视频编辑新纪元:MoviePy库的神秘面纱

文章目录 探索 Python 视频编辑新纪元&#xff1a;MoviePy 库的神秘面纱第一部分&#xff1a;背景介绍第二部分&#xff1a;MoviePy 是什么&#xff1f;第三部分&#xff1a;如何安装这个库&#xff1f;第四部分&#xff1a;简单的库函数使用方法第五部分&#xff1a;结合场景使…

计算机网络:网络层 —— 多播路由选择协议

文章目录 多播路由选择协议多播转发树构建多播转发树基于源树的多播路由选择建立广播转发树建立多播转发树 组共享树的多播路由选择基于核心的生成树的建立过程 因特网的多播路由选择协议 多播路由选择协议 仅使用 IGMP 并不能在因特网上进行IP多播。连接在局域网上的多播路由…

Jenkins插件使用问题总结

Git Push插件 插件介绍 主要是用于git推送代码到远程仓库中使用&#xff0c;插件地址 pipeline中使用 官方说明中只有一句代码gitPush(gitScm: scm, targetBranch: env.BRANCH_NAME, targetRepo: origin) 流水线语法中也做的不齐全所以一开始我老是设置错&#xff0c;导致代…

【命令操作】Linux三剑客之awk详解 _ 统信 _ 麒麟 _ 方德

原文链接&#xff1a;【命令操作】Linux三剑客之awk详解 | 统信 | 麒麟 | 方德 Hello&#xff0c;大家好啊&#xff01;今天带来一篇关于Linux三剑客之awk命令详解的文章。在文本处理工具中&#xff0c;awk以其强大的文本筛选、格式化和数据处理功能而闻名。它能够在处理结构化…

操作系统概念(一)——IOMMU学习

系列文章目录 提示&#xff1a;本系列主要记录工作过程中遇到的操作系统基础概念以及工作原理 第一章 操作系统之IOMMU 文章目录 系列文章目录1. 设备访问内存的几种主要方式1.1 传统的 I/O 访问&#xff08;程序控制 I/O&#xff09;1.2 直接内存访问&#xff08;DMA&#xf…

计算机网络:网络层 —— IP 多播技术

文章目录 基本概念IP多播地址和多播组 IP多播的类型硬件多播将IPv4多播地址映射为多播MAC地址 基本概念 多播&#xff08;Multicast&#xff0c;也称为组播&#xff09;是一种实现“一对多”通信的技术&#xff0c;允许一台或多台主机&#xff08;多播源&#xff09;发送单一数…

windows运行ffmpeg的脚本报错:av_ts2str、av_ts2timestr、av_err2str => E0029 C4576

问题描述 我目前的环境是&#xff1a; 编辑器&#xff1a; Microsoft Visual Studio Community 2022 (64 位) 运行的脚本是ffmpeg自带的remux样例&#xff0c;只不过我想用c语言执行这个样例。在执行的过程中报错如下图&#xff1a; C4576 后跟初始值设定项列表的带圆括…

翻译工具开发技术笔记:《老挝语翻译通》app支持语音识别翻译功能,怎么提高语音识别的准确度呢?

《老挝语翻译通》app是一款专为老挝语翻译设计的免费工具&#xff0c;支持文本翻译、老挝文OCR文字识别提取、文字转语音。这款工具以其技术优势和用户友好的界面&#xff0c;为用户提供了便捷的老挝语翻译体验。 技术特点 文本翻译&#xff1a;支持双语输入&#xff0c;提供精…

Linux系统每日定时备份mysql数据

一、创建存储脚本的文件夹 创建文件夹&#xff0c;我的脚本放在/root/dbback/mysql mkdir ... cd /root/dbback/mysql 二、编写脚本 vi backup_mysql.sh 复制脚本内容 DB_USER"填写用户名" DB_PASSWORD"填写密码" DB_NAME"数据库名称" # …

MySQL基础-单表查询

语法 select [distinct] 列名1&#xff0c;列名2 as 别名... from数据表名 where组前筛选 group by分组字段 having组后筛选 order by排序的列 [asc | desc] limit 起始索引&#xff0c;数据条数 测试数据 # 建测试表 create table products (id int primary key a…

【Linux】Linux管道揭秘:匿名管道如何连接进程世界

&#x1f308;个人主页&#xff1a;Yui_ &#x1f308;Linux专栏&#xff1a;Linux &#x1f308;C语言笔记专栏&#xff1a;C语言笔记 &#x1f308;数据结构专栏&#xff1a;数据结构 &#x1f308;C专栏&#xff1a;C 文章目录 1.什么是管道 &#xff1f;2. 管道的类型2.1 匿…

前端学习Day13 CSS盒子的定位(固定定位篇“附练习”)

一、固定定位 固定定位 &#xff08;position:fixed&#xff09;其实是绝对定位的子类别&#xff0c;一个设置了 position:fixed 的元素是相对于视窗固定的&#xff0c;就算页面文档发生了滚动&#xff0c;它也会一直待在相同的地方。 ⚠️&#xff1a;固定定位会脱离文档流。…

Linux云计算 |【第五阶段】CLOUD-DAY9

主要内容&#xff1a; Metrics资源利用率监控、存储卷管理&#xff08;临时卷ConfitMap、EmptyDir、持久卷HostPath、NFS(PV/PVC)&#xff09; 一、Metrics介绍 metrics是一个监控系统资源使用的插件&#xff0c;可以监控Node节点上的CPU、内存的使用率&#xff0c;或Pod对资…

BM25:最佳匹配 ,文本相关性评分算法

目录 BM25:最佳匹配 一、BM25算法原理 二、BM25算法的应用场景 三、BM25算法的举例说明 BM25:最佳匹配 BM25(Best Matching 25)是一种在信息检索领域中广泛使用的文本相关性评分算法。它基于概率模型,考虑了词频(Term Frequency,TF)、逆文档频率(Inverse Documen…