第十四讲:答疑文章(一):日志和索引相关问题

news/2024/12/22 19:15:05/文章来源:https://www.cnblogs.com/guixiangyyds/p/18402220

第十四讲:答疑文章(一):日志和索引相关问题

简概:

img

​ 到目前为止,我已经收集了 47 个问题,很难通过今天这一篇文章全部展开。所以,我就先从中找了几个联系非常紧密的问题,串了起来,希望可以帮你解决关于日志和索引的一些疑惑。而其他问题,我们就留着后面慢慢展开吧。

​ 我在第 2 篇文章《日志系统:一条 SQL 更新语句是如何执行的?》中,和你讲到 binlog(归档日志)和 redo log(重做日志)配合崩溃恢复的时候,用的是反证法,说明了如果没有两阶段提交,会导致 MySQL 出现主备数据不一致等问题。

日志相关问题:

第一问:在不同阶段提交过程中, 如果发生异常重启,是怎么保证数据完整性的

​ 在这篇文章下面,很多同学在问,在两阶段提交的不同瞬间,MySQL 如果发生异常重启,是怎么保证数据完整性的?现在,我们就从这个问题开始吧。我再放一次两阶段提交的图,方便你学习下面的内容。

img

​ 这里,我要先和你解释一个误会式的问题。有同学在评论区问到,这个图不是一个 update 语句的执行流程吗,怎么还会调用 commit 语句?他产生这个疑问的原因,是把两个“commit”的概念混淆了

​ 他说的“commit 语句”,是指 MySQL 语法中,用于提交一个事务的命令。一般跟 begin/start transaction 配对使用。而我们图中用到的这个“commit 步骤”,指的是事务提交过程中的一个小步骤,也是最后一步。当这个步骤执行完成后,这个事务就提交完成了。“commit 语句”执行的时候,会包含“commit 步骤”。

​ 接下来,我们就一起分析一下在两阶段提交的不同时刻,MySQL 异常重启会出现什么现象。

​ 如果在图中时刻 A 的地方,也就是写入 redo log 处于 prepare 阶段之后、写 binlog 之前,发生了崩溃(crash),由于此时 binlog 还没写,redo log 也还没提交,所以崩溃恢复的时候,这个事务会回滚。这时候,binlog 还没写,所以也不会传到备库。到这里,大家都可以理解。

​ 大家出现问题的地方,主要集中在时刻 B,也就是 binlog 写完,redo log 还没 commit 前发生 crash,那崩溃恢复的时候 MySQL 会怎么处理?

​ 我们先来看一下崩溃恢复时的判断规则。

  • 如果 redo log 里面的事务是完整的,也就是已经有了 commit 标识,则直接提交;
  • 如果 redo log 里面的事务只有完整的 prepare,则判断对应的事务 binlog 是否存在并完整:
    • a. 如果是,则提交事务;
    • b. 否则,回滚事务。

​ 这里,时刻 B 发生 crash 对应的就是 2(a) 的情况,崩溃恢复过程中事务会被提交

​ 现在,我们继续延展一下这个问题。

追问 1:MySQL 怎么知道 binlog 是完整的?

​ 回答:一个事务的 binlog 是有完整格式的:

  • statement 格式的 binlog,最后会有 COMMIT;
  • row 格式的 binlog,最后会有一个 XID event。

​ 另外,在 MySQL 5.6.2 版本以后,还引入了 binlog-checksum 参数,用来验证 binlog 内容的正确性。对于 binlog 日志由于磁盘原因,可能会在日志中间出错的情况,MySQL 可以通过校验 checksum 的结果来发现。所以,MySQL 还是有办法验证事务 binlog 的完整性的。

追问 2:redo log 和 binlog 是怎么关联起来的?

​ 回答:它们有一个共同的数据字段,叫 XID

​ 崩溃恢复的时候,会按顺序扫描 redo log:

  • 如果碰到既有 prepare、又有 commit 的 redo log,就直接提交;
  • 如果碰到只有 prepare、而没有 commit 的 redo log,就拿着 XID 去 binlog 找对应的事务。

[!tip]

redolog和binlog的两阶段提交 1、如果redolog有prepare,binlog根据checkSum判断是否写入成功,失败就回滚redolog日志 2、redolog有prepare,binlog写入成功,redolog在commit的时候发生crash,恢复的时候,顺序扫描redolog,发现redolog有prepare,却没有提交,拿着XID去binlog找对应的事务.

xid 关联redo 和 binlog

追问 3:处于 prepare 阶段的 redo log 加上完整 binlog,重启就能恢复,MySQL 为什么要这么设计?

​ 回答:其实,这个问题还是跟我们在反证法中说到的数据与备份的一致性有关。

​ 在时刻 B,也就是 binlog 写完以后 MySQL 发生崩溃,这时候 binlog 已经写入了,之后就会被从库(或者用这个 binlog 恢复出来的库)使用。所以,在主库上也要提交这个事务。采用这个策略,主库和备库的数据就保证了一致性。

​ 其实说白了,两个日志都有自己的用处,所以我们设计出两阶段提交,保证两种日志的数据一致性,只要两种日志的数据是一致的,就可以提交事务

追问 4:如果这样的话,为什么还要两阶段提交呢?干脆先 redo log 写完,再写 binlog。崩溃恢复的时候,必须得两个日志都完整才可以。是不是一样的逻辑?

​ 回答:其实,两阶段提交是经典的分布式系统问题,并不是 MySQL 独有的。

​ 如果必须要举一个场景,来说明这么做的必要性的话,那就是事务的持久性问题。对于 InnoDB 引擎来说,如果 redo log 提交完成了,事务就不能回滚(如果这还允许回滚,就可能覆盖掉别的事务的更新)。而如果 redo log 直接提交,然后 binlog 写入的时候失败,InnoDB 又回滚不了,数据和 binlog 日志又不一致了。两阶段提交就是为了给所有人一个机会,当每个人都说“我 ok”的时候,再一起提交。

[!caution]

​ 其实把Mysql的两阶段提交也可以看成两个分布式服务处理两个不同事情redo log在Innodb引擎内操作的,binlog是在server层操作的,我们就可以把引擎层和server层看成两个分布式服务,那他们要分别进行两个相关联的操作,就意味着要实现分布式事务,而两阶段提交,就是其中的一种解决方案

​ 也就是先写 redo log ,然后写 bin log ,再提交redo log。

追问 5:不引入两个日志,也就没有两阶段提交的必要了。只用 binlog 来支持崩溃恢复,又能支持归档,不就可以了?

​ 回答:这位同学的意思是,只保留 binlog,然后可以把提交流程改成这样:… -> “数据更新到内存” -> “写 binlog” -> “提交事务”,是不是也可以提供崩溃恢复的能力?

​ 答案是不可以。

​ 如果说历史原因的话,那就是 InnoDB 并不是 MySQL 的原生存储引擎。MySQL 的原生引擎是 MyISAM,设计之初就有没有支持崩溃恢复。InnoDB 在作为 MySQL 的插件加入 MySQL 引擎家族之前,就已经是一个提供了崩溃恢复和事务支持的引擎了。InnoDB 接入了 MySQL 后,发现既然 binlog 没有崩溃恢复的能力,那就用 InnoDB 原有的 redo log 好了。而如果说实现上的原因的话,就有很多了。就按照问题中说的,只用 binlog 来实现崩溃恢复的流程,我画了一张示意图,这里就没有 redo log 了。

img

​ 这样的流程下,binlog 还是不能支持崩溃恢复的

​ 我说一个不支持的点吧:binlog 没有能力恢复“数据页”。

​ 如果在图中标的位置,也就是 binlog2 写完了,但是整个事务还没有 commit 的时候,MySQL 发生了 crash。重启后,引擎内部事务 2 会回滚,然后应用 binlog2 可以补回来;但是对于事务 1 来说,系统已经认为提交完成了,不会再应用一次 binlog1

​ 但是,InnoDB 引擎使用的是 WAL 技术,执行事务的时候,写完内存和日志,事务就算完成了。如果之后崩溃,要依赖于日志来恢复数据页。也就是说在图中这个位置发生崩溃的话,事务 1 也是可能丢失了的,而且是数据页级的丢失。此时,binlog 里面并没有记录数据页的更新细节,是补不回来的。

​ 你如果要说,那我优化一下 binlog 的内容,让它来记录数据页的更改可以吗?但,这其实就是又做了一个 redo log 出来。所以,至少现在的 binlog 能力,还不能支持崩溃恢复。

[!tip]

redo log 会记录具体数据页上的更改项;而 binlog 是逻辑变更记录

​ 这里意思可以举个例子更直观,如:binlog1是update c+1;binlog2是update c+1;现在在binlog2写完没提交的时候发生crash,这时对数据的更新可能还停留在内存中,并未刷盘,crash后内存数据丢失。 由于binlog2事务为完成,系统会应用binlog2恢复数据,即此时c+1;但对于binlog1来说,已经完成了事务,系统不会再应用binlog1来恢复数据,所以数据c不会再+1. 这时数据c只加了一次,与未crash前c+了两次不同 即binlog没有能力恢复数据页。。。。。在图中这个位置崩溃的话,事务1可能只是redolog写进磁盘并且提交了,但是事务1更新的记录并没有刷盘,也就是丢失了。 但是恢复的时候我们只用binlog来恢复,这时候事务1显示是提交的,所以不会应用binlog,导致这块数据就丢失了。 因为binlog并不记录数据页级的丢失。 如果真想使用binlog来恢复的话,那么就要在每个commit之前,将更改的内存记录刷盘。刷盘之后再将这个事务改为commit状态。 这样崩溃恢复就可以在事务级去做了,而不用在数据页级去做了。

​ 前置条件: 假设没有redo log,只有binlog

​ 问题1: 只有binlog是否可以在发生crash后执行数据完整性恢复?

​ 解答: 如文中所讲,binglog1 和 binglog2 是两个事务中的语句,分别执行update c+1 操作,按照前文中讲的mysql执行步骤,会先看这条记录所在的内存页是否在内存中,如果在内存中,直接更新内存,事务执行完成,返回用户更新结果。

​ 注意这个时候内存中的数据还没有执行flush操作; 所以如图所示binlog1的虽然事务提交了,但是数据还在内存中,如果发生了crash,由于没有记录数据页更新的操作,则恢复的时候只能通过binglog日志的未提交进行恢复,也就是恢复binglog2的操作

问题2: 那binlog如何执行crash恢复呢?

​ 解答: 没办法恢复,我设想了一些场景,即使每一次binlog都执行刷盘,那如果刷完盘更改binglog为commit标识之前系统崩溃了,那么数据还是不一致的。

​ 问题3: 引入redolog为什么就能执行崩溃恢复了呢?

​ 解答: 先写redolog,redolog记录了这次操作在哪一个数据页做了什么操作,进入prepare阶段,然后写binlog,binlog写入完成后,那么就相当于事务提交成功了

​ 注意:这个地方有一点就是prepare的redo log 刷盘时机,如果innodb_flush_log_at_trx_commit 是0或者2,那么由于redo log 记录数据页的操作,可能还在内存中,恢复的时候redo log就会少一个prepare记录,则这个没有commit的数据就丢失了。

追问 6:那能不能反过来,只用 redo log,不要 binlog?

​ 回答:如果只从崩溃恢复的角度来讲是可以的。你可以把 binlog 关掉,这样就没有两阶段提交了,但系统依然是 crash-safe 的。

​ 但是,如果你了解一下业界各个公司的使用场景的话,就会发现在正式的生产库上,binlog 都是开着的。

​ 因为 binlog 有着 redo log 无法替代的功能。

  • 一个是归档。redo log 是循环写,写到末尾是要回到开头继续写的。这样历史日志没法保留,redo log 也就起不到归档的作用

  • 一个就是 MySQL 系统依赖于 binlog。binlog 作为 MySQL 一开始就有的功能,被用在了很多地方。其中,MySQL 系统高可用的基础,就是 binlog 复制。

​ 还有很多公司有异构系统(比如一些数据分析系统),这些系统就靠消费 MySQL 的 binlog 来更新自己的数据。关掉 binlog 的话,这些下游系统就没法输入了。总之,由于现在包括 MySQL 高可用在内的很多系统机制都依赖于 binlog,所以“鸠占鹊巢”redo log 还做不到。你看,发展生态是多么重要。

追问 7:redo log 一般设置多大?

​ 回答:redo log 太小的话,会导致很快就被写满,然后不得不强行刷 redo log,这样 WAL 机制(预写日志)的能力就发挥不出来了。所以,如果是现在常见的几个 TB 的磁盘的话,就不要太小气了,直接将 redo log 设置为 4 个文件、每个文件 1GB 吧。

追问 8:正常运行中的实例,数据写入后的最终落盘,是从 redo log 更新过来的还是从 buffer pool 更新过来的呢?

​ 回答:这个问题其实问得非常好。

​ 这里涉及到了,“redo log 里面到底是什么”的问题。实际上,redo log 并没有记录数据页的完整数据,所以它并没有能力自己去更新磁盘数据页,也就不存在“数据最终落盘,是由 redo log 更新过去”的情况。

下面是两种情况:

  1. 如果是正常运行的实例的话,数据页被修改以后,跟磁盘的数据页不一致,称为脏页。
  2. 最终数据落盘,就是把内存中的数据页写盘。这个过程,甚至与 redo log 毫无关系。在崩溃恢复场景中,InnoDB 如果判断到一个数据页可能在崩溃恢复的时候丢失了更新,就会将它读到内存,然后让 redo log 更新内存内容。更新完成后,内存页变成脏页,就回到了第一种情况的状态

[!tip]

redo log只负责将内存数据更新成最新的,然后再刷脏页,而不是由redo log直接恢复数据,它没有这个能力

数据落盘是内存【脏页】flush的时候写入的。 2. 在崩溃恢复时, 如果InnoDB判断一个数据页可能丢失了更新,就会将它读到内存,然后让redo log更新内存内容。 更新完成后, 内存页变成了脏页,也就是到了1的状态。

追问 9:redo log buffer 是什么?是先修改内存,还是先写 redo log 文件?

​ 回答:这两个问题可以一起回答。在一个事务的更新过程中,日志是要写多次的。比如下面这个事务:

begin;
insert into t1 ...
insert into t2 ...
commit;

​ 这个事务要往两个表中插入记录,插入数据的过程中,生成的日志都得先保存起来,但又不能在还没 commit 的时候就直接写到 redo log 文件里

​ 所以,redo log buffer 就是一块内存,用来先存 redo 日志的。也就是说,在执行第一个 insert 的时候,数据的内存被修改了,redo log buffer 也写入了日志。但是,真正把日志写到 redo log 文件(文件名是 ib_logfile+ 数字),是在执行 commit 语句的时候做的。(这里说的是事务执行过程中不会“主动去刷盘”,以减少不必要的 IO 消耗。但是可能会出现“被动写入磁盘”,比如内存不够、其他事务提交等情况。这个问题我们会在后面第 22 篇文章《MySQL 有哪些“饮鸩止渴”的提高性能的方法?》中再详细展开)。

​ 单独执行一个更新语句的时候,InnoDB 会自己启动一个事务,在语句执行完成的时候提交。过程跟上面是一样的,只不过是“压缩”到了一个语句里面完成。

​ 以上这些问题,就是把大家提过的关于 redo log 和 binlog 的问题串起来,做的一次集中回答。如果你还有问题,可以在评论区继续留言补充。

业务设计问题:

​ 问题是这样的:

​ 业务上有这样的需求,A、B 两个用户,如果互相关注,则成为好友。

​ 设计上是有两张表,一个是 like 表,一个是 friend 表,like 表有 user_id、liker_id 两个字段,我设置为复合唯一索引即 uk_user_id_liker_id。

​ 语句执行逻辑是这样的:以 A 关注 B 为例:第一步,先查询对方有没有关注自己(B 有没有关注 A)

select * from like where user_id = B and liker_id = A;

​ 如果有,则成为好友insert into friend;

​ 没有,则只是单向关注关系insert into like;

​ 但是如果 A、B 同时关注对方,会出现不会成为好友的情况。

​ 因为上面第 1 步,双方都没关注对方。第 1 步即使使用了排他锁也不行,因为记录不存在,行锁无法生效。 请问这种情况,在 MySQL 锁层面有没有办法处理?

​ 接下来,我把 同学说的表模拟出来,方便我们讨论

CREATE TABLE `like` (`id` int(11) NOT NULL AUTO_INCREMENT,`user_id` int(11) NOT NULL,`liker_id` int(11) NOT NULL,PRIMARY KEY (`id`),UNIQUE KEY `uk_user_id_liker_id` (`user_id`,`liker_id`)
) ENGINE=InnoDB;CREATE TABLE `friend` (`id` int(11) NOT NULL AUTO_INCREMENT,`friend_1_id` int(11) NOT NULL,`friend_2_id` int(11) NOT NULL,UNIQUE KEY `uk_friend` (`friend_1_id`,`friend_2_id`),PRIMARY KEY (`id`)
) ENGINE=InnoDB;

​ 虽然这个题干中,并没有说到 friend 表的索引结构。

​ 但我猜测 friend_1_id 和 friend_2_id 也有索引,为便于描述,我给加上唯一索引。顺便说明一下,“like”是关键字,我一般不建议使用关键字作为库名、表名、字段名或索引名。

​ 我把他的疑问翻译一下,在并发场景下,同时有两个人,设置为关注对方,就可能导致无法成功加为朋友关系。现在,我用你已经熟悉的时刻顺序表的形式,把这两个事务的执行语句列出来:

img

​ 由于一开始 A 和 B 之间没有关注关系,所以两个事务里面的 select 语句查出来的结果都是空。

​ 因此,session 1 的逻辑就是“既然 B 没有关注 A,那就只插入一个单向关注关系”。

​ session 2 也同样是这个逻辑。

​ 这个结果对业务来说就是 bug 了。因为在业务设定里面,这两个逻辑都执行完成以后,是应该在 friend 表里面插入一行记录的。

​ 如提问里面说的,“第 1 步即使使用了排他锁也不行,因为记录不存在,行锁无法生效”。不过,我想到了另外一个方法,来解决这个问题。

​ 首先,要给“like”表增加一个字段,比如叫作 relation_ship,并设为整型,取值 1、2、3。

  • 值是 1 的时候,表示 user_id 关注 liker_id;
  • 值是 2 的时候,表示 liker_id 关注 user_id;
  • 值是 3 的时候,表示互相关注。

​ 然后,当 A 关注 B 的时候,逻辑改成如下所示的样子:

​ 应用代码里面,比较 A 和 B 的大小,如果 A<B,就执行下面的逻辑

mysql> begin; /*启动事务*/
insert into `like`(user_id, liker_id, relation_ship) values(A, B, 1) on duplicate key update relation_ship=relation_ship | 1;
select relation_ship from `like` where user_id=A and liker_id=B;
/*代码中判断返回的 relation_ship,如果是1,事务结束,执行 commit如果是3,则执行下面这两个语句:*/
insert ignore into friend(friend_1_id, friend_2_id) values(A,B);
commit;

[!tip]

这里的AB,并不是确切的用户a和用户b,而是如方程里面的变量x,y,假设: 第一次的时候,a的id位3,b的id为4,当a关注b时,(此时a对应的是文中的A,b对应的是文中的B)由于A的id小于B的id,所以采用 insert into like(user_id, liker_id, relation_ship) values(A, B, 1),此时 like 表中 user_id = 3,liker_id 为4,relation_ship = 1。 第二次的时候,当b关注a时,(此时b对应的是文中的A,a对应的是文中的B),由于A的id大于B的id,所以采用insert into like(user_id, liker_id, relation_ship) values(B, A, 2),B对应的id为3,A对应的id为4,relation_ship = 2,由于采用了insert … on duplicate语句,此时relation_ship采用位运算 1 | 2 ,结果是3,即用户a与用户b是相互喜欢,变成了事件3,就会在friend 表中添加两者相互喜欢的记录。

​ 如果 A>B,则执行下面的逻辑

mysql> begin; /*启动事务*/
insert into `like`(user_id, liker_id, relation_ship) values(B, A, 2) on duplicate key update relation_ship=relation_ship | 2;
select relation_ship from `like` where user_id=B and liker_id=A;
/*代码中判断返回的 relation_ship,如果是2,事务结束,执行 commit如果是3,则执行下面这两个语句:
*/
insert ignore into friend(friend_1_id, friend_2_id) values(B,A);
commit;

[!tip]

也就是无论A关注B,还是B关注A,存在表里的数据都会是相同的,要么AB,要么BA,主要靠relation_ship判断是谁关注谁

​ 这个设计里,让“like”表里的数据保证 user_id < liker_id,这样不论是 A 关注 B,还是 B 关注 A,在操作“like”表的时候,如果反向的关系已经存在,就会出现行锁冲突。

​ 然后,insert … on duplicate 语句,确保了在事务内部,执行了这个 SQL 语句后,就强行占住了这个行锁,之后的 select 判断 relation_ship 这个逻辑时就确保了是在行锁保护下的读操作。

​ 操作符 “|” 是按位或,连同最后一句 insert 语句里的 ignore,是为了保证重复调用时的幂等性。

​ 这样,即使在双方“同时”执行关注操作,最终数据库里的结果,也是 like 表里面有一条关于 A 和 B 的记录,而且 relation_ship 的值是 3, 并且 friend 表里面也有了 A 和 B 的这条记录。

​ 不知道你会不会吐槽:之前明明还说尽量不要使用唯一索引,结果这个例子一上来我就创建了两个。这里我要再和你说明一下,之前文章我们讨论的,是在“业务开发保证不会插入重复记录”的情况下,着重要解决性能问题的时候,才建议尽量使用普通索引。而像这个例子里,按照这个设计,业务根本就是保证“我一定会插入重复数据,数据库一定要要有唯一性约束”,这时就没啥好说的了,唯一索引建起来吧。

问题

我们创建了一个简单的表 t,并插入一行,然后对这一行做修改。

mysql> CREATE TABLE `t` (
`id` int(11) NOT NULL primary key auto_increment,
`a` int(11) DEFAULT NULL
) ENGINE=InnoDB;
insert into t values(1,2);

​ 这时候,表 t 里有唯一的一行数据 (1,2)。

​ 假设,我现在要执行:

mysql> update t set a=2 where id=1;

​ 你会看到这样的结果:

img

​ 结果显示,匹配 (rows matched) 了一行,修改 (Changed) 了 0 行。

​ 仅从现象上看,MySQL 内部在处理这个命令的时候,可以有以下三种选择:

  1. 更新都是先读后写的,MySQL 读出数据,发现 a 的值本来就是 2,不更新,直接返回,执行结束;

  2. MySQL 调用了 InnoDB 引擎提供的“修改为 (1,2)”这个接口,但是引擎发现值与原来相同,不更新,直接返回;

  3. InnoDB 认真执行了“把这个值修改成 (1,2)"这个操作,该加锁的加锁,该更新的更新。

​ 你觉得实际情况会是以上哪种呢?你可否用构造实验的方式,来证明你的结论?进一步地,可以思考一下,MySQL 为什么要选择这种策略呢?

答案:

​ 第一个选项是,MySQL 读出数据,发现值与原来相同,不更新,直接返回,执行结束。这里我们可以用一个锁实验来确认。假设,当前表 t 里的值是 (1,2)。

img

​ session B 的 update 语句被 blocked 了,加锁这个动作是 InnoDB 才能做的,所以排除选项 1。

​ 第二个选项是,MySQL 调用了 InnoDB 引擎提供的接口,但是引擎发现值与原来相同,不更新,直接返回。有没有这种可能呢?

​ 这里我用一个可见性实验来确认。假设当前表里的值是 (1,2)。

img

​ session A 的第二个 select 语句是一致性读(快照读),它是不能看见 session B 的更新的。

​ 现在它返回的是 (1,3),表示它看见了某个新的版本,这个版本只能是 session A 自己的 update 语句做更新的时候生成。(如果你对这个逻辑有疑惑的话,可以回顾下第 8 篇文章《事务到底是隔离的还是不隔离的?》中的相关内容)

​ 所以,我们思考题的答案应该是选项 3,即:InnoDB 认真执行了“把这个值修改成 (1,2)"这个操作,该加锁的加锁,该更新的更新。

​ 然后你会说,MySQL 怎么这么笨,就不会更新前判断一下值是不是相同吗?如果判断一下,不就不用浪费 InnoDB 操作,多去更新一次了?其实 MySQL 是确认了的。只是在这个语句里面,MySQL 认为读出来的值,只有一个确定的 (id=1), 而要写的是 (a=3),只从这两个信息是看不出来“不需要修改”的。

​ 作为验证,你可以看一下下面这个例子。

img

​ 上面只通过id=1判断不出不需要更新图上where条件添加a=3,可以判断出不需要更新 WHERE里面有a和SET的a相同,UPDATE执行时发现了这点,于是直接返回,故没有新增任何修改记录(由SHOW ENGINE INNODB STATUS的LSN可以证明当WHERE和SET相同时,UPDATE不会执行)

​ 可见返回值中的changed很重要!

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

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

相关文章

大模型api实战-open.bigmodel.cn

注册登录后在个人中心的API keys中找到并复制推荐使用SDK,在虚拟环境安装 pip install zhipuai编辑python代码访问API获取响应 from zhipuai import ZhipuAI client = ZhipuAI(api_key="0c6df39e71b0a7340f221fddc1ddb711.au66Z02fXWc7SJBB") response = client.cha…

焦煤

这种走势概率大 目前在走3-5的跌势

linux虚拟机(centos)搭建sqli-labs

1.开启小皮2.查看文件位置 配置文件路径为/usr/local/phpstudy/soft [root@localhost soft]# cd /www/admin/localhost_80 [root@localhost soft]# pwd /usr/local/phpstudy/soft网站根目录为/www/admin/localhost_80/wwwroot [root@localhost localhost_80]# cd wwwroot [root…

Zabbix01 Zabbix安装和基础功能

商业监控方案#从各个地区来监测网络情况 http://ping.chinaz.com/ 站长之家 免费 https://www.jiankongbao.com/ 监控宝 ...#云服务自带云监控系统 Zabbix 架构#zabbix web为php程序 如果公司规模小,zabbix server,db和zabbix web装在一台机器上 如果公司规模大,…

【赛后反思】洛谷基础赛 #15 「LAOI」Round 6 考后总结(待补完)

待补完待补完待补完待补完待补完待补完待补完待补完待补完待补完待补完待补完待补完待补完待补完待补完待补完待补完待补完待补完待补完待补完待补完待补完待补完待补完待补完待补完待补完LGR-198-Div.3 考后总结 又要掉分了:展开目录 目录LGR-198-Div.3 考后总结A [太阳]] 请…

Win10电脑网络正常,其他浏览器可以打开网页,但Chrome浏览器打不开网页,开发者工具中看请求未发出,左上角一直转圈圈

问题现象: Win10电脑网络正常,可以ping通baidu.com, qq.com, 域名正常解析。 其他浏览器edge可以打开网页 但Chrome浏览器打不开网页,开发者工具中看请求未发出,左上角一直转圈圈解决办法: 谷歌浏览器右上角,点击三个点按钮-->然后选择设置,高级 --> 系统 -->…

[c++][笔记]浅谈几种排序方式---冒泡排序,选择排序,桶排序

一、algorithm里的sort函数 #include <cstdio> // 数据小的可以用iostream #include <algorithm> // 不能忘记算法库,否则会编译失败。 using namespace std; int main() {int n;scanf("%d", &n);int a[n+5] = {};for (int i = 1; i <= n; i++)…

Java反序列化漏洞-TemplatesImpl利用链分析

目录一、前言二、正文1. 寻找利用链2. 构造POC2.1 生成字节码2.2 加载字节码1)getTransletInstance2)defineTransletClasses2.3 创建实例3. 完整POC三、参考文章 一、前言 java.lang.ClassLoader#defineClassdefineClass可以加载字节码,但由于defineClass的作用域是protecte…

Camunda Modeler流程设计器

1、介绍 任何可执行流程都需要预先设计和配置业务流程模型和BPMN图,BPMN图可以让使用者更容易理解流程的结构,Camunda Modeler是一个可视化设计和实现BPMN图表的工具。 下面是官方使用文档:1、Modeler中绘制BPMN介绍 2、桌面版Modeler使用介绍 2、相关概念 可以将BPMN的绘制…

【工具推荐】KillWxapkg v2.4(最新版) - 自动化反编译微信小程序,小程序安全评估工具

工具介绍: 纯Golang实现,一个用于自动化反编译微信小程序的工具,小程序安全利器,自动解密,解包,可还原工程目录,支持微信开发者工具运行 下载链接: 链接:https://pan.quark.cn/s/aa5480be4bd5使用说明 工程结构还原 还原前还原后微信开发者工具运行看着就真的看着,不…

Agent(智能体)和 MetaGPT,一句话实现整个需求应用代码

本文介绍了大模型 Agent 定义、组成部分,并以 MetaGPT 多智能体为例,一句话完成贪吃蛇小游戏需求,以介绍整个智能体的工作流程……前面 2 篇文章,我们使用文生文、文生图和文生音频三个大模型共同实现了图文并茂的儿童绘本故事和绘本故事音频需求:第一篇 根据主题生成儿童…

html的表单和初始js

1.表单是html常用的一类,我们平时使用的收集账号密码填写信息都是表单,标签是form,含有属性action和method,action确定表单接受数据的地址,不写默认为网页本身.method有两种收集方式,"post"和"get",其中默认方式为get,但是get对接收信息的大小有限制,post没…