MySQL日志(三):数据安全

先来看一个结论:只要redo log和binlog保证持久化到磁盘, 就能确保MySQL异常重启后, 数据可以恢复。

binlog写入逻辑


binlog的写入逻辑比较简单: 事务执行过程中, 先把日志写到binlog cache, 事务提交的时候, 再把binlog cache写到binlog文件中,并清空binlog cache。

在这个过程中,系统给binlog cache分配了一片内存,线程私有,参数binlog_cache_size用于控制单个线程内binlog cache所占内存的大小。如果超过了这个参数规定的大小,就要暂存到磁盘。

binlog写入逻辑状态图如下:

可以看到, 每个线程有自己binlog cache, 但是共用同一份binlog文件。

  • 图中的write, 指的就是指把日志写入到文件系统的page cache, 并没有把数据持久化到磁盘, 所以速度比较快。(掉电会丢数据)
  • 图中的fsync, 才是将数据持久化到磁盘的操作。 一般情况下, 我们认为fsync才占磁盘的IOPS(每秒的读写次数)。(掉电不丢数据)

write 和fsync的时机, 是由参数sync_binlog控制的:

1)sync_binlog=0的时候, 表示每次提交事务都只write, 不fsync。

2)sync_binlog=1的时候, 表示每次提交事务都会执行fsync。

3)sync_binlog=N(N>1)的时候, 表示每次提交事务都write, 但累积N个事务后才fsync。

因此, 在出现IO瓶颈的场景里, 将sync_binlog设置成一个比较大的值, 可以提升性能。 在实际的业务场景中, 考虑到丢失日志量的可控性, 一般不建议将这个参数设成0, 比较常见的是将其设置为100~1000中的某个数值。

注1:一个事务的binlog是不能被拆开的,因此不论这个事务多大,也要确保一次性写入。

注2:每个线程有自己binlog cache,但是共用同一份binlog file。

redo log写入逻辑


1)redo log写入逻辑

  • 事务执行过程中,生成的redo log会先写到redo log buffer。(内存)
  • redo log buffer中的日志并不一定会立即持久化到磁盘。
  • 当节点宕机或者系统掉电后,redo log buffer中的日志丢失,但由于事务并没有提交,binlog日志未落盘,所以这时日志丢了也不会有损失。
  • InnoDB有一个后台线程,每隔1秒会把redo log buffer中的日志,flush到文件系统的page cache中,然后调用fsync持久化到磁盘。

注:redo log buffer是全局共用的。

2)redo log可能的状态

  • 存在redo log buffer中,即进程内存中,对应图中红色部分。
  • 写到磁盘(flush),但未持久化(fsync),物理上是在文件系统的page cache里面, 即图中的黄色部分。
  • 持久化到磁盘,对应的是hard disk,即图中的绿色部分。(一般fsync才会占用磁盘的IOPS,所以较慢)

注:日志写到redo log buffer是很快的, wirte到page cache也差不多, 但是持久化到磁盘的速度就慢多了。

3)为了控制redo log的写入策略, InnoDB提供了innodb_flush_log_at_trx_commit参数, 它有三种可能取值:

  • 设置为0,表示每次事务提交时都只是把redo log留在redo log buffer中。
  • 设置为1,表示每次事务提交时都将redo log直接持久化到磁盘(把redo log buffer中的所有日志全部持久化到磁盘)。
  • 设置为2,表示每次事务提交时都只是把redo log写到page cache。

注1:InnoDB有一个后台线程, 每隔1秒, 就会把redo log buffer中的日志, 调用write写到文件系统的page cache, 然后调用fsync持久化到磁盘。

注2:事务执行过程中的redo log也直接写在redo log buffer中的,这些redo log也会被后台线程一起持久化到磁盘。也就是说,一个没有提交的事务的redo log也可能已经被持久化到磁盘。

问1:除了后台线程每秒一次的轮询操作外,还有什么场景会让一个没有提交的事务的redo log写入磁盘?

  • 一种是, redo log buffer占用的空间即将达到 innodb_log_buffer_size一半的时候(即redo log buffer使用率过半时),后台线程会主动写盘。 注意, 由于这个事务并没有提交, 所以这个写盘动作只是write, 而没有调用fsync, 也就是只留在了文件系统的page cache。
  • 另一种是, 并行的事务提交的时候, 顺带将这个事务的redo log buffer持久化到磁盘。 假设一个事务A执行到一半, 已经写了一些redo log到buffer中, 这时候有另外一个线程的事务B提交, 如果innodb_flush_log_at_trx_commit设置的是1, 那么按照这个参数的逻辑, 事务B要把redo log buffer里的日志全部持久化到磁盘。 这时候, 就会带上事务A在redo log buffer里的日志一起持久化到磁盘。

注1:如果把innodb_flush_log_at_trx_commit设置成1, 那么redo log在prepare阶段就要持久化一次,因为有一个崩溃恢复逻辑是要依赖于prepare 的redo log, 再加上binlog来恢复的。

注2:每秒一次后台轮询刷盘, 再加上崩溃恢复这个逻辑, InnoDB就认为redo log在commit的时候就不需要fsync了, 只会write到文件系统的page cache中就够了。

问2:如果一个事务提交,需要等待两次刷盘,这是不是意味着从MySQL看到的TPS是每秒两万的话, 每秒就会写四万次磁盘。 但是, 我用工具测试出来, 磁盘能力也就两万左右, 怎么能实现两万的TPS?

答:通过组提交实现。

组提交机制


通常我们说MySQL的“双1”配置, 指的就是sync_binlog和innodb_flush_log_at_trx_commit都设置成 1。 也就是说, 一个事务完整提交前, 需要等待两次刷盘, 一次是redo log(prepare 阶段) , 一次是binlog。

问1:日志逻辑序列号(log sequence number, LSN) 是什么?

LSN是单调递增的, 用来对应redo log的一个个写入点。 每次写入长度为length的redo log, LSN的值就会加上length。

LSN也会写到InnoDB的数据页中, 来确保数据页不会被多次执行重复的redo log。

如下图所示,是三个并发事务(trx1, trx2, trx3)在prepare 阶段, 都写完redo log buffer, 持久化到磁盘的过程, 对应的LSN分别是50、 120 和160。

从图中可以看到:

1)trx1是第一个到达的, 会被选为这组的 leader。

2)等trx1要开始写盘的时候, 这个组里面已经有了三个事务, 这时候LSN也变成了160。

3)trx1去写盘的时候, 带的就是LSN=160, 因此等trx1返回时, 所有LSN小于等于160的redolog, 都已经被持久化到磁盘。

4)这时候trx2和trx3就可以直接返回了。(即trx2和trx3的redo log都被trx1 redo log持久化到磁盘时,一并持久化了)

所以, 一次组提交里面, 组员越多, 节约磁盘IOPS的效果越好。 但如果只有单线程压测, 那就只能老老实实地一个事务对应一次持久化操作了。

也就是说,在并发更新场景下, 第一个事务写完redo log buffer以后, 接下来这个fsync越晚调用, 组员可能越多, 节约IOPS的效果就越好。

为了让一次fsync带的组员更多, MySQL有一个很有趣的优化: 拖时间。如下图所示。

图中, 我把“写binlog”当成一个动作。 但实际上, 写binlog是分成两步的:

1)先把binlog从binlog cache中写到磁盘上的binlog文件。

2)调用fsync持久化。

MySQL为了让组提交的效果更好, 把redo log做fsync的时间拖到了步骤1之后。 也就是说, 上面的图变成了这样:

这么一来, binlog也可以组提交了。 在执行图中第4步把binlog fsync到磁盘时, 如果有多个事务的binlog已经写完了(即多个事务已提交), 也是一起持久化的, 这样也可以减少IOPS的消耗。

不过通常情况下第3步执行得会很快, 所以binlog的write和fsync间的间隔时间短, 导致能集合到一起持久化的binlog比较少, 因此binlog的组提交的效果通常不如redo log的效果那么好。

问2:如何提升binlog组提交的效果?

答:可以通过设置 binlog_group_commit_sync_delay和binlog_group_commit_sync_no_delay_count来实现。

1)binlog_group_commit_sync_delay参数, 表示延迟多少微秒后才调用fsync。(强行拖延时间)

2)binlog_group_commit_sync_no_delay_count参数, 表示累积等待的事务数量以后才调用fsync。

注1:这两个条件是或的关系, 也就是说只要有一个满足条件就会调用fsync。

注2:当binlog_group_commit_sync_delay设置为0的时候, binlog_group_commit_sync_no_delay_count也无效了。

问3:WAL机制是减少磁盘写, 可是每次提交事务都要写redo log和binlog, 这磁盘读写次数也没变少呀?

答:WAL机制主要得益于两个方面:

1)redo log 和 binlog都是顺序写, 磁盘的顺序写比随机写速度要快。

2)组提交机制, 可以大幅度降低磁盘的IOPS消耗。

问4:如果你的MySQL现在出现了性能瓶颈, 而且瓶颈在IO上, 可以通过哪些方法来提升性能呢?

答:针对这个问题, 可以考虑以下三种方法:

1)设置 binlog_group_commit_sync_delay和 binlog_group_commit_sync_no_delay_count参数, 减少binlog的写盘次数。 这个方法是基于“额外的故意等待”来实现的, 因此可能会增加语句的响应时间, 但没有丢失数据的风险。

2)将sync_binlog 设置为大于1的值(比较常见是100~1000) 。 这样做的风险是, 主机掉电时会丢binlog日志。

3)将innodb_flush_log_at_trx_commit设置为2。 这样做的风险是, 主机掉电的时候会丢数据。

注1:不建议把innodb_flush_log_at_trx_commit 设置成0。 因为把这个参数设置成0, 表示redo log只保存在内存中, 这样的话MySQL本身异常重启也会丢数据, 风险太大。 而redo log写到文件系统的page cache的速度也是很快的, 所以将这个参数设置成2跟设置成0其实性能差不多,但这样做MySQL异常重启时就不会丢数据了, 相比之下风险会更小。

注2:redo log在内存中,MySQL异常重启会丢失数据。redo log在page cache(文件系统)中,MySQL异常重启不会丢失数据,但主机掉电会丢失数据。

小结:什么是WAL?


回答这个问题前,先看一下磁盘、SSD、内存随机读写与顺序读写的性能对比,如下图所示:

从上图可以看出两点:

1)内存的读写速度比磁盘随机读写高出几个数量级。

2)磁盘顺序读写速度堪比内存读写速度。

问1:什么是WAL呢?

WAL即 Write Ahead Log,WAL的核心思想是,每次更新操作都先写入日志,只做了写日志这一个磁盘操作。这个日志叫做redo log(重做日志),也就是《孔乙己》记账的粉板,在更新完内存写完redo log后,就返回给客户端,本次更新成功。而实际更新操作是由后台线程根据redo log异步写入。因此,对Client来说,延迟会减少。同时,顺序写入的可能性很大,如果更新的多条记录位于同一Page,则磁盘IO次数也能大大降低。

所以,WAL机制主要得益于三个方面:

1)减少更新数据的磁盘IO。

2)redo log 和 binlog都是顺序写, 磁盘的顺序写比随机写速度要快很多。

3)组提交机制, 可以大幅度降低磁盘的IOPS消耗。

问2:WAL和两阶段提交的区别是什么?

因此,WAL技术的关键是把随机写转化为顺序写,减少磁盘IO次数,以此提升数据库性能。

而两阶段提交是为了保证分布式事务架构数据的一致性,通常由redo log和binlog来保证。(具体什么是两阶段提交,在本文后面叙述)

注:使用redo log记录变更后的操作,使用undo log记录变更前的数据(用于回滚)。日志写入成功后,事务不会丢失。

小结:思考题


思考1:redo log和binlog是怎么关联起来的?

它们有一个共同的数据字段,叫XID。

1)崩溃恢复的时候,会先扫描最近的binlog,获取所有已提交的XID列表。

2)之后按顺序扫描redo log。

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

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

其实, 这个问题还是跟我们在反证法中说到的数据与备份的一致性有关。 在时刻B, 也就是binlog写完以后MySQL发生崩溃, 这时候binlog已经写入了, 之后就会被从库(或者用这个binlog恢复出来的库) 使用。所以, 在主库上也要提交这个事务。 采用这个策略, 主库和备库的数据就保证了一致性。

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

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

如果必须要举一个场景, 来说明这么做的必要性的话, 那就是事务的持久性问题。对于InnoDB引擎来说, 如果redo log提交完成了, 事务就不能回滚(如果这还允许回滚, 就可能覆盖掉别的事务的更新) 。 而如果redo log直接提交, 然后binlog写入的时候失败, InnoDB又回滚不了, 数据和binlog日志又不一致了。

两阶段提交就是为了给所有人一个机会, 当每个人都说“我ok”的时候, 再一起提交。

思考4:redo log一般设置多大?

redo log太小的话,会导致很快就被写满,然后不得不强行刷redo log,这样WAL机制的能力就发挥不出来了。

所以,如果是现在常见的几个TB的磁盘的话,直接将redo log设置为4个文件、每个文件1GB。

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

redo log并没有记录数据页的完整数据,所以它并没有能力自己去更新磁盘数据页,也就不存在“数据最终落盘,是由redo log更新过去”的情况。

1)如果是正常运行的实例,数据页被修改以后,跟磁盘的数据页不一致,称为脏页。最终数据落盘,就是把内存中的数据页写盘。这个过程,基本与redo log毫无关系。

2)在崩溃恢复场景中,InnoDB如果判断到一个数据页可能在崩溃恢复的时候丢失了更新,就会将它读到内存,然后让redo log更新内存内容。更新完成后,内存页变成脏页,就回到了第一种情况的状态。

注:加载数据页的时候是以page为单位,所以数据落盘的时候也是以page为单位。

思考6:执行update语句以后,去执行hexdump命令直接查看ibd文件内容,为什么没有看到数据有改变呢?

这可能是因为WAL机制的原因。update语句执行完成后,InnoDB只保证写完了redo log、内存,可能还没来得及将数据写到磁盘。

思考7:为什么binlog cache是每个线程自己维护的,而redo log buffer是全局共用的?

1)binlog方面的原因:

  • MySQL这么设计的主要原因是,binlog是不能“被打断的”(如果binlog cache是全局的,则可能会导致binlog交叉写入)。一个事务的binlog必须连续写,因此要整个事务完成后,再一起写到文件里。

2)redo log方面的原因:

  • 而redo log并没有这个要求,中间有生成的日志可以写到redo log buffer中。redo log buffer中的内容还能“搭便车”,其他事务提交的时候可以被一起写到磁盘中。
  • redo log的主要目的是确保事务的持久性和崩溃恢复,这个目的是全局性的,因此redo log buffer设计为全局共享是合理的。
  • binlog存储是以statement或者row格式存储的,而redo log是以page页格式存储的。page格式,天生就是共有的,而row格式,只跟当前事务相关。

思考8:如果binlog写完盘以后发生crash,这时候还没给客户端答复就重启了。等客户端再重连进来,发现事务已经提交成功了,这是不是bug?

不是。实际上数据库的crash-safe保证的是:

1)如果客户端收到事务成功的消息,事务就一定持久化了。

2)如果客户端收到事务失败(比如主键冲突、回滚等)的消息,事务就一定失败了。

3)如果客户端收到“执行异常”的消息,应用需要重连后通过查询当前状态来继续后续的逻辑。此时数据库只需要保证内部(数据和日志之间,主库和备库之间)一致就可以了。

思考9:sync_binlog和binlog_group_commit_sync_no_delay_count有什么区别?

1)sync_binlog = N:该参数用于binlog持久化策略,每个事务write后就响应客户端了。刷盘是N次事务后刷盘。N次事务之间宕机,数据丢失。

2)binlog_group_commit_sync_no_delay_count = N:该参数用于组提交策略,必须等到N个事务后才能提交。换言之,会增加响应客户端的时间。但是一旦响应了,那么数据就一定持久化了。宕机的话,数据是不会丢失的。该配置先于sync_binlog执行。

思考10:如果sync_binlog = N、binlog_group_commit_sync_no_delay_count = M、binlog_group_commit_sync_delay = 很大值,这种情况下何时刷盘?

答:达到N次以后, 可以刷盘了, 然后再进入(sync_delay和no_delay_count)这个逻辑。Sync_delay如果很大, 就达到no_delay_count才刷。

思考11: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语句的时候做的。

思考12:你的生产库设置的是“双1”吗? 如果平时是的话, 你有在什么场景下改成过“非双1”吗? 你的这个操作又是基于什么决定的?

常见需要设置“非双1”场景如下:

1)业务高峰期。 一般如果有预知的高峰期, DBA会有预案, 把主库设置成“非双1”。

2)备库延迟, 为了让备库尽快赶上主库。

3)用备份恢复主库的副本, 应用binlog的过程, 这个跟上一种场景类似。

4)批量导入数据的时候。

一般情况下, 把生产库改成“非双1”配置, 是设置innodb_flush_logs_at_trx_commit=2、sync_binlog=1000。

注:“双1”设置虽然能够减少磁盘IO,但增加了相应时间。

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

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

相关文章

Linux:线程池

Linux:线程池 线程池概念封装线程基本结构构造函数相关接口线程类总代码 封装线程池基本结构构造与析构初始化启动与回收主线程放任务其他线程读取任务终止线程池测试线程池总代码 线程池概念 线程池是一种线程使用模式。线程过多会带来调度开销,进而影…

STM32CubeMX配置-外部中断配置

一、简介 MCU为STM32G070,配置为上升沿触发外部中断,在上升沿外部中断回调函数中进行相关操作。 二、外部中断配置 查看规格书中管教描述,找到I/O对应的外部中断线,然后进行如下上升沿触发外部中断配置。 三、生成代码 调用上升沿…

JavaScript 规范霍夫曼编码

霍夫曼编码是一种无损数据压缩算法,其中数据中的每个字符都分配有可变长度的前缀代码。出现频率最低的字符获得最大代码,出现频率最高的字符获得最小代码。使用这种技术对数据进行编码非常简单且高效。但是,解码使用此技术生成的比特流效率低…

Parallels Desktop 19 激活码 - 苹果 Mac 最新版 PD 19激活密钥虚拟机下载 (支持Win11/macOS Sonoma)

Parallels Desktop 被称为 macOS 上强大的虚拟机软件。可以在 Mac 下同时模拟运行 Win、Linux、Android 等多种操作系统及软件而不必重启电脑,并能在不同系统间随意切换。 最新版 Parallels Desktop 19 (PD19) 完全支持 macOS Sonoma、Ventura 和 Windows 11 / Win…

【Ardiuno】实验使用OPT语音模块播放语音(图文)

当我们需要在程序中播放语音内容时,就需要使用到语音模块,今天我们就来实验一下使用OPT语音模块来方法语音。 const int voicePin 5; const int voiceBusyPin 18; const int testLEDPin 2;unsigned long pmillis 0;int busyVal 0; …

LeetCode | 125.验证回文串

这道题一开始的想法是把原字符串的非数字英文字符去掉,然后判断剩下的字符串是否为回文串即可,其中去掉非数字英文字符可以遍历一遍字符串依次处理,也可以用正则表达式,然后判断是否是回文串只需要两个指针,一头一尾&a…

OpenCV目标识别

一 图像轮廓 具有相同颜色或强度的连续点的曲线。 图像轮廓的作用 可以用于图像分析 物体的识别与检测 注意 为了检测的准确性,需要先对图像进行二值化或Canny操作。 画轮廓时会修改输入的图像。 轮廓查找的API findContours(img,mode,ApproximationMode,...)…

upload-labs第八关教程

upload-labs第八关教程 一、源代码分析代码审计 二、绕过分析点绕过上传eval.php使用burp suite进行抓包修改放包,查看是否上传成功使用中国蚁剑进行连接 一、源代码分析 代码审计 $is_upload false; $msg null; if (isset($_POST[submit])) {if (file_exists(U…

推箱子-小游戏

学习目标: 巩固Java基础,数据类型、二维数组、条件语句等; 效果展示:

CSS【详解】样式选择器的优先级(含提升优先级的方法)

数值越大,优先级越高,尽量保持较低的优先级,以便使用更高优先级的选择器重置样式 0级——通配选择器、选择符和逻辑组合伪类。逻辑组合伪类有:not()、:is()和:where等,这些伪类本身并不影响CSS优先级,影响优先级的是括…

Python基础用法 之 变量

1.变量的定义 变量的作用:是⽤来保存数据的。定义的语法:变量名 数据值使用:直接使⽤变量名 即可使⽤变量中存储的数据。注意:变量必须先定义后使用。 (即 必须 先存⼊数据 才能 获取数据) 。 # 需求 1, 定义⼀个变量 保存你的名…

(超详细)基于动态顺序表实现简单的通讯录项目

前言: 我们在上一章节用c语言实现了线性表中的的动态顺序表,那么顺序表就只是顺序表吗?当然不是,使用顺序表结构可以实现很多项目,许多项目的数据结构都会用到顺序表,本章节我们就要使用顺序表实现一个简易…

【论文阅读】AttnDreamBooth | 面向文本对齐的个性化图片生成

文章目录 1 动机2 方法3 实验 1 动机 使用灵活的文本控制可以实现一些特定的概念的注入从而实现个性化的图片生成。 最经典的比如一些好玩的动漫人物的概念,SD大模型本身是不知道这些概念的,但是通过概念注入是可以实现的从而生成对应的动漫人物 两个…

创建阿里云的免费镜像仓库

1、登录 阿里云 首先进入阿里云的官网,如果没有注册的需要先注册,这里就不过多的讲解了。 2、搜索 登录完毕后点击右上角的控制台 进入管理页面。或者直接在搜索框中输入容器镜像服务 点击进入 这里我是已经开通过了,如果你还没有开通的…

SpringBoot 第一天

什么是Spring Boot 学习过spring,并且做过项目的估计都经历过,xml文件的繁杂配置,让人眼花缭乱,且极易出错,因此 Spring 一度被称为“配置地狱” 为了简化 Spring 应用的搭建和开发过程,Pivotal 团队在 S…

什么是git?

前言 Git 是一款免费、开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目。是的,我对git的介绍就一条,想看简介的可以去百度一下😘😘😘 为什么要用git? OK,想象一下…

《C语言》文件操作

文章目录 一、认识文件1、文件的概念2、程序文件3、数据文件4、文件名 三、二进制文件和文本文件四、文件的打开和关闭1、流2、标准流3、文件指针4、文件的关闭和打开 四、文件的顺序读写文件的随机读写1、fseek2、ftell3、rewind4.int origin 一、认识文件 主要讨论数据文件 1…

Javaweb06-Jsp技术

Jsp技术 一.Jsp的运行原理 **概述:**JSP是Java服务器页面,既可以写静态页面代码,也可以写动态页面代码 **特点:**跨平台性,业务代码相分离,组件重用,预编译 运行原理: 客户端发生…

如何设计一个秒杀系统?

这篇分享源自之前购买的极客时间课程《如何设计一个秒杀系统》,以及书籍《亿级流量网站架构核心技术》。 这两个讲的都是关于高并发系统设计的,感觉收获颇多。 本篇内容对核心要点进行了摘录,也结合网上一些文章,希望能分享所得…

运算符及表达式+基本语句和函数使用的详细讲解

运算符及表达式 运算符及表达式 在C语言中,运算符是用于执行特定操作的符号,而表达式则是由运算符和操作数组成的式子。 1) 运算符 运算符的目数 单目运算符:只需要一个操作数,如 !(逻辑非)、&#xf…