news 2026/10/19 5:21:27

Oracle审计日志清理实战:AUD$表膨胀排查与表空间治理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Oracle审计日志清理实战:AUD$表膨胀排查与表空间治理

项目标题: 重庆思庄oracle技术分享-oracle 审计日志清理

项目正文: 关于Oracle数据库审计日志清理的技术分享,主要讨论审计表空间膨胀、AUD$表清理、审计日志轮转等数据库运维问题。

关键词: Oracle审计, 审计日志清理, AUD$表, 审计表空间, 数据库运维

摘要描述: 分享Oracle数据库审计日志膨胀的排查思路、清理实操方法与长期治理方案。 干这行久了你会发现,Oracle数据库的告警十有八九绕不开两个词:表空间满了、归档满了。而这两个“满”背后,又有一大半的根源是同一个东西——审计日志。我刚接手某生产系统的时候,第一眼就被吓到了,一个纯OLTP业务库,AUD$表居然占了将近300GB,系统表空间差点被撑爆,数据库直接进入告警状态。后来花了一整个下午清理、复盘、调参数,才算把这只“吞空间的怪兽”按回去。

这篇东西就是冲着这个来的,把Oracle审计日志从膨胀原理、排查思路、清理实操到长期轮转方案一条龙讲清楚。不管你是刚接触Oracle的新手DBA,还是被审计日志折磨过的老运维,这篇都能给你一点可以直接抄作业的思路。

1. 审计日志为什么说涨就涨

1.1 审计日志的存储机制

很多初学者会把“审计”理解成简单地在告警日志里写几行字,实则不然。Oracle的审计体系在设计上就自带一张实打实的数据库表——SYS.AUD$,所有标准审计记录默认都往这张表里写。这张表在哪个表空间里,问题就在哪个表空间里炸。

Oracle开启审计的方式很直观,一套参数下来,符合条件的操作就会生成一条记录。举个例子:

-- 查看当前审计相关参数 SHOW PARAMETER AUDIT_TRAIL; SHOW PARAMETER AUDIT_SYS_OPERATIONS;

AUDIT_TRAIL的值有几种:NONE表示关闭,OS表示写到操作系统审计文件,DB表示写入AUD$表,DB_EXTENDED则是在DB的基础上额外记录SQL文本和绑定变量。一旦设置为DB或DB_EXTENDED,所有被审计的操作都会往AUD$表里塞数据。如果审计策略还比较激进,比如审计了所有登录、所有DDL、所有DML,那这张表的膨胀速度就不会跟你商量了。

1.2 审计膨胀的典型推手

结合我实际碰到的情况,审计日志“失控”最常见的几类原因:

  • 审计策略过宽。比如AUDIT ALL BY某用户,甚至直接对全库开启细粒度审计(FGA),每一个普通查询都会落一条记录。
  • 应用连接池频繁重建连接。很多Java应用用了连接池,但如果池参数配得不合理,每隔一会儿就重新登录一次,每次登录都会被审计,记录量直接翻倍。
  • 批量任务集中在夜间跑批,跑批期间产生的审计记录占全天的大头。
  • 长期不清理的“历史遗留”。AUD$表本来就有几十GB,清理策略一直没跟上,老账新账叠一起。

用一句话概括:审计日志失控的本质,是写入速度和清理速度长期不匹配,最后撑爆表空间。你如果现在打开数据库发现SYSTEM表空间使用率超过了90%,第一反应真不应该是“扩表空间”,而是先查AUD$表占地多少,这是我在实战中血泪换来的经验。

1.3 什么时候该清、什么时候该忍

不是说审计日志一涨就要立刻动手。你得先判断数据库当前的状态:

  • 如果表空间使用率在80%以下,且增长速度平稳,可以先观察,同时准备清理方案。
  • 如果表空间使用率已经超过90%,甚至报警了,没得选,立刻启动紧急清理流程。
  • 如果AUD$表所在表空间是SYSTEM(默认就是),那就更危险,因为SYSTEM表空间还承担着数据字典等重要功能,它满了不仅是写不了审计日志的问题,整个库都可能出状况。

碰到这种情况,我建议你优先确认一件事:数据库是否开启了审计。有些库可能只是默认开启的强制审计(比如SYS用户的某些操作),AUD$表虽然增长慢,但常年累月也会膨胀,不要被“我们没开审计”这种说法带偏,先查参数再下结论。

提示:拿到一个告警库,先跑一句SELECT * FROM DBA_AUDIT_TRAIL WHERE ROWNUM < 5;看看有没有数据,再查SELECT COUNT(*) FROM SYS.AUD$;评估量级,比拍脑袋靠谱多了。

2. 清理前的三查三看

2.1 查大小、查增长、查分布

动手清理之前,先花几分钟把“敌情”摸清楚。你连AUD$表多大都不知道就贸然TRUNCATE,我见过有人直接把整个SYSTEM表空间搞成只读的,那才是真事故。

核心查询就三句:

-- 查看AUD$表占用了多少空间 SELECT SEGMENT_NAME, BYTES/1024/1024/1024 AS SIZE_GB FROM DBA_SEGMENTS WHERE SEGMENT_NAME IN ('AUD$', 'FGA_LOG$'); -- 查看审计记录总量及最早/最晚记录时间 SELECT COUNT(*), MIN(NTIMESTAMP#), MAX(NTIMESTAMP#) FROM SYS.AUD$; -- 按天统计审计记录量 SELECT TO_CHAR(NTIMESTAMP#, 'YYYY-MM-DD') AS DAY, COUNT(*) FROM SYS.AUD$ GROUP BY TO_CHAR(NTIMESTAMP#, 'YYYY-MM-DD') ORDER BY DAY DESC;

注意,审计表里除了AUD$,还有FGA_LOG$(细粒度审计日志表)。很多DBA只盯着AUD$,结果FGA_LOG$在SYSTEM表空间里偷偷长到几十GB,这属于典型的“灯下黑”。

2.2 查审计策略,找到膨胀源头

清理是一次性的,但如果你不把源头堵住,过两周又得再清一回。我每次都会查当前启用的审计策略,看看到底是谁在“制造垃圾”:

-- 查看数据库级统一审计策略(Oracle 12c及以上) SELECT POLICY_NAME, AUDIT_OPTION, CONDITION_EVAL_OPT FROM AUDIT_UNIFIED_ENABLED_POLICIES; -- 查看传统审计设置 SELECT AUDIT_OPTION, SUCCESS, FAILURE FROM DBA_STMT_AUDIT_OPTS; -- 查看FGA策略 SELECT OBJECT_SCHEMA, OBJECT_NAME, POLICY_NAME, ENABLED FROM DBA_AUDIT_POLICIES;

这步的意义在于:你要分清哪些审计是合规要求必须保留的,哪些是当初调试遗留的。我遇到过有个系统,整整三年前为了排查某个问题开了一条“审计所有DML”的策略,问题解决后策略忘了关,三年攒了200GB的审计记录,就问你夸张不夸张。

2.3 查归档与备份策略,想清楚“后果”

清理AUD$表之前,必须想清楚:这些审计记录还需要保留吗?如果要保留,保留多久?这不仅是技术问题,还涉及合规。虽然我们不做政策评论,但审计日志在很多场景里就是“事后追溯”的依据。

实操层面我的建议是:

  • 如果业务上有追溯需求,先做备份,比如把AUD$表按时间分区抽出来,EXPORT到历史表或外部文件,再做清理。
  • 如果纯粹是垃圾数据,比如连“查询次数统计”这种毫无价值的记录都攒了好几年,直接清理没问题。
  • 如果数据库开启了归档模式,且归档空间不容乐观,大规模DELETE会产生大量归档日志,这个账也要提前算。

注意:审计日志的清理不是越激进越好。频繁的大批量DELETE会带来UNDO膨胀、归档暴涨、索引碎片化等多米诺骨牌效应。后面我会详细拆解每种清理方式的利弊,你先别急着动手。

3. 三种主流清理方案逐一拆解

3.1 方案一:TRUNCATE——快刀斩乱麻

如果你确认审计记录不需要保留,或者已经完成了备份,最干净利落的方式就是TRUNCATE。

-- 清空AUD$表 TRUNCATE TABLE SYS.AUD$; -- 清空FGA日志表 TRUNCATE TABLE SYS.FGA_LOG$;

为什么推荐TRUNCATE而不是DELETE?两个原因:

第一,TRUNCATE是DDL操作,不走UNDO,不产生UNDO膨胀;第二,TRUNCATE直接释放段空间(加上DROP STORAGE选项的话),表空间使用率立竿见影地降下来。DELETE则会产生大量UNDO和REDO,跑几个小时都有可能,期间还会把归档空间顶爆。

但TRUNCATE有前置条件:不能有外键引用、不能有正在进行的活动事务。实际操作中常见的问题是,某些系统会从AUD$表做数据抽取(ETL同步审计信息到大数据平台),如果有视图或者物化视图基于AUD$表刷新,TRUNCATE就可能报ORA-00054“资源正忙”。这种时候需要跟业务方确认一下,或者挑业务低峰期操作。

这段实操经验值得记下来:我习惯在TRUNCATE之前先做一次计数和空间占用快照,清理后对比次数和空间,方便跟领导汇报“清了多少、释放了多少”。干运维,干活是次要的,交代清楚才是核心。

3.2 方案二:分批DELETE——兼顾留痕与稳定

如果你的审计记录还有保留价值,比如需要留最近3个月的,那就不能一把TRUNCATE全清。这时候用分批DELETE是比较稳的方案。

核心思路:按时间范围,每次删一小批(比如5万条或10万条),然后提交,循环执行。这样做的好处是控制UNDO压力,避免一次删除百万级数据把回滚段撑爆。

-- 分批删除指定时间之前的审计记录,每批删除5万条 DECLARE V_COUNT NUMBER := 1; BEGIN WHILE V_COUNT > 0 LOOP DELETE FROM SYS.AUD$ WHERE NTIMESTAMP# < TO_TIMESTAMP('2024-01-01', 'YYYY-MM-DD') AND ROWNUM <= 50000; V_COUNT := SQL%ROWCOUNT; COMMIT; END LOOP; END; /

这里有几个细节要特别注意:

  • 不能只靠DELETE,删完还要重建表的高水位线,也就是执行ALTER TABLE SYS.AUD$ MOVE;或者SHRINK SPACE,否则表空间使用率不会明显下降,只是表里的行变少了。很多人清完发现“空间没释放”,就是栽在这个地方。

  • 如果你不知道AUD$表怎么重建索引,至少要在MOVE之后重建一下索引,避免后续审计写入出现索引性能问题。标准操作是ALTER INDEX SYS.AUDIT_PK REBUILD;这种依赖索引名的,需要先查一下实际索引名。

  • 删除过程中别忘了监控UNDO表空间使用率。如果UNDO空间紧张,请降低每批删除的行数,或者干脆换TRUNCATE方案。

为什么说这个方案适合“留痕”?因为你可以只把N个月以前的记录清掉,保留最近一段时间的审计数据。这个时间窗口根据各家业务诉求来定,有的留3个月,有的留半年,没有绝对标准,但建议跟安全团队确认清楚,别到时候需要追溯却拿不出数据,那就尴尬了。

3.3 方案三:切换新表再归档——生产环境的体面操作

TRUNCATE太粗暴,DELETE太耗时,那有没有一种方案既不影响业务、又能干干净净腾出空间的?

有:把AUD$表改成重定向到新表,旧表直接归档。这个方案尤其适合AUD$表已经几十GB乃至上百GB,用DELETE根本不现实,而你又不想承担TRUNCATE风险的大库。

思路是这样的:

  1. 先建一张新表AUD$NEW,结构和AUD$完全一致。
  2. 停止审计写入(如果可以接受短暂停审计的话),或者直接利用Oracle 12c+的审计表重定向功能。

Oracle从12.1开始就支持用DBMS_AUDIT_MGMT包做审计表的分区和归档,不过底层思路还是要理解。简单讲,就是用DBMS_AUDIT_MGMT.SET_AUDIT_TRAIL_LOCATION把审计表重定向到一个新表,然后老表就可以放心离线归档了。

实际操作我推荐一个“伪重定向”的手段,在生产上也验证过:

-- 创建与AUD$结构一致的新表 CREATE TABLE SYS.AUD$_BAK AS SELECT * FROM SYS.AUD$ WHERE 1=0; -- 通过同义词或表重命名方式切换(具体视版本而定) -- 注意:此操作极其敏感,必须先停审计或确认写入中断的容忍度

重命名系统审计表这个操作,很多生产环境是不允许的。更稳妥的做法是:在业务低谷窗口,先用AUDIT_TRAIL='NONE'参数临时关闭审计或用NOAUDIT关掉具体策略,然后做TRUNCATE或MOVE,操作完成后再把审计打开。这种“临时闪断”的方式,比在审计表上做复杂手术要安全得多。

实操中我更常用的组合拳是:先确认能接受中断审计几分钟,然后临时停止审计写入,TRUNCATE AUD$表,重建索引,最后恢复审计。整个过程控制在几分钟内完成,系统表空间立刻瘦身,代价最小。

4. 清理实操中经常踩的坑

4.1 表被锁了,TRUNCATE直接卡住

这个坑我踩过一次大的。当时收到表空间告警,评估下来需要TRUNCATE AUD$表,结果TRUNCATE一执行就卡住不动了。查了半天,发现某监控工具每隔5分钟会查一次AUD$表做审计统计,查询会话一直开着,导致TRUNCATE拿不到锁。

排查办法:

-- 查看哪些会话在用AUD$相关对象 SELECT S.SID, S.SERIAL#, S.STATUS, S.USERNAME, S.MACHINE, S.PROGRAM FROM V$SESSION S WHERE S.SQL_ADDRESS IN ( SELECT CURRENT_OBJ FROM V$ACCESS WHERE OWNER = 'SYS' AND OBJECT_NAME = 'AUD$' );

找到占用会话后,跟业务侧确认是否可以杀掉。如果不知道该会话是干嘛的,可以先SELECT * FROM V$SESSION_LONGOPS看看有没有在跑大查询,谨防误杀核心业务的会话。

提示:如果TRUNCATE一直卡住,不要反复去执行新的TRUNCATE。后面的语句都会排队等锁,只会把数据库搞得更乱。先杀锁,再重新评估。

4.2 DELETE太猛,UNDO撑爆了

分批DELETE的时候,我见过有人一上来就写DELETE FROM AUD$ WHERE ...;不带ROWNUM限制,一条语句删几千万行。结果UNDO表空间瞬间打满,数据库直接挂起。

这个问题的根源是对UNDO机制的理解不透:DELETE每删一行,原始数据的镜像就要写进UNDO,删的行越多,UNDO占用越大。就算你COMMIT了,UNDO里的数据在满足保留时间之前也不会立即释放。

保险的做法:

-- 删除前先查UNDO表空间使用率 SELECT TABLESPACE_NAME, SUM(BYTES)/1024/1024 AS USED_MB FROM DBA_UNDO_EXTENTS WHERE STATUS IN ('ACTIVE','UNEXPIRED') GROUP BY TABLESPACE_NAME;

如果UNDO使用率已经超过70%,我一般会把每批删除量从5万降到1万甚至5000。慢一点没关系,稳定第一。生产环境的审计清理,宁可跑一晚上,也不要赌一条大SQL能秒完。

4.3 清完空间没释放,表空间还是满的

很多人清完AUD$表,发现表空间的“剩余空间”跟清理前差不多,于是怀疑自己操作无效。其实这很可能是高水位线问题。

DELETE语句不会降低表的高水位线,即使表里一行数据都没有了,段空间依然被之前存储过的数据占用着。所以DELETE之后必须做空间收缩:

-- 移动表并降低高水位线 ALTER TABLE SYS.AUD$ MOVE; -- 重建表的索引(MOVE之后索引会失效) ALTER INDEX SYS.AUD$ PK_REBUILD;

MOVE操作会重建表并释放段空间,但这个操作本身是DDL,执行期间表不可用。所以你必须在业务低谷时段操作,否则会中断正在执行的审计写入。

这里有一个经验之谈:TRUNCATE天然会重置高水位线,所以如果你确认数据不需要保留,TRUNCATE比“DELETE+MOVE”这套组合高效得多。麻烦的是保留数据的需求,就必须靠MOVE来释放空间。

4.4 清理之后没过多久又满了

这种情况比“清不干净”更让人头疼,说明你的清理只是治标没治本。审计日志增长的速度如果大于你清理的速度,那再大的表空间都会变成无底洞。

排查重点放在两个地方:审计策略是不是开得太宽了、归档日志里面有多少策略是无效的。我遇到过一个场景,开发为了排查一个偶发报错,给某核心表开了一条FGA策略,记录所有UPDATE语句的绑定变量。排查结束后FGA策略没删,每秒钟几百条UPDATE就往审计表里灌。这种如果不堵源头,你一天清一次都清不完。

所以我的经验是:审计清理工作,三分在“清”,七分在“防”。

5. 从救火到防火:审计日志的长期治理方案

5.1 审计参数调优,在源头控增量

这一步值得你花点时间认真调一下。审计不是越全越好,而是越准越好。审计记录的价值是“事后可追溯”,不是为了给表空间添堵。

几个实用的参数调整思路:

-- 调整审计日志写入模式(需要重启实例,谨慎操作) -- DB_EXTENDED会记录SQL文本,空间消耗远大于DB ALTER SYSTEM SET AUDIT_TRAIL=DB SCOPE=SPFILE; -- 关闭不必要的FGA策略或缩小策略范围 BEGIN DBMS_FGA.DROP_POLICY( object_schema => 'APP_USER', object_name => 'T_ORDER', policy_name => 'T_ORDER_AUDIT' ); END; /

经过评估,如果业务只需要知道谁在什么时候登录、执行了什么DDL,那就没必要审计所有DML。把审计策略从“全记录”改成“关键操作记录”,日志量能下降一个数量级。

5.2 定期自动清理与窗口任务

长期维护不能靠人工每月手点一次,一定要脚本化、自动化。我的思路是写一个存储过程,每个月自动执行一次审计日志清理,只保留最近N个月的审计记录。

下面是我常用的清理脚本模板(简化后),你可以根据自己的情况定制:

CREATE OR REPLACE PROCEDURE PRC_CLEAN_AUDIT_LOG AS V_KEEP_MONTHS NUMBER := 3; -- 保留近3个月 V_CUTOFF_DATE TIMESTAMP; V_COUNT NUMBER := 1; BEGIN -- 计算保留截止时间 V_CUTOFF_DATE := SYSTIMESTAMP - INTERVAL '3' MONTH; -- 分批删除过期的审计记录 WHILE V_COUNT > 0 LOOP DELETE FROM SYS.AUD$ WHERE NTIMESTAMP# < V_CUTOFF_DATE AND ROWNUM <= 20000; V_COUNT := SQL%ROWCOUNT; COMMIT; END LOOP; -- 收缩AUD$表空间 EXECUTE IMMEDIATE 'ALTER TABLE SYS.AUD$ MOVE'; -- 重建索引 EXECUTE IMMEDIATE 'ALTER INDEX SYS.AUD$_PK REBUILD'; END; /

把这个存储过程挂到DBMS_SCHEDULER的 monthly job 里,每个月固定时间执行一次。要注意的坑是:JOB执行时间和业务高峰要错开,最好选在凌晨三四点这种业务空窗期;同时JOB执行期间要监控UNDO表空间和归档空间,防止意外。

5.3 分区表方案,一劳永逸

如果咱们退一步想,最优雅的审计日志管理方案其实是把AUD$表改成按月分区的分区表。分区表天然支持按分区TRUNCATE/DROP的快捷操作,清理某个月的数据就是一句话的事,既不用大DELETE,也不用重建高水位线。

不过,AUD$表是Oracle系统表,想改成分区表的动作不简单,需要依赖DBMS_AUDIT_MGMT包来做分区管理。12c以上版本的统一审计(Unified Auditing)已经默认把审计记录写到SYSAUX表空间而不是SYSTEM了,本身就缓解了SYSTEM表空间的压力。

Oracle统一审计里有一整套清理命令:

-- 创建审计清理任务(12c+统一审计模式) BEGIN DBMS_AUDIT_MGMT.INIT_CLEANUP( AUDIT_TRAIL_TYPE => DBMS_AUDIT_MGMT.AUDIT_TRAIL_UNIFIED, DEFAULT_CLEANUP_INTERVAL => 24 ); END; / -- 设置审计记录保留时间 BEGIN DBMS_AUDIT_MGMT.SET_LAST_ARCHIVE_TIMESTAMP( AUDIT_TRAIL_TYPE => DBMS_AUDIT_MGMT.AUDIT_TRAIL_UNIFIED, LAST_ARCHIVE_TIME => SYSTIMESTAMP - INTERVAL '3' MONTH ); END; / -- 执行清理 BEGIN DBMS_AUDIT_MGMT.CLEAN_AUDIT_TRAIL( AUDIT_TRAIL_TYPE => DBMS_AUDIT_MGMT.AUDIT_TRAIL_UNIFIED ); END; /

这套命令的好处是全自动、按保留时间管理、不需要手动DELETE。如果你们库是12c以上版本,强烈建议直接用统一审计的清理机制,省心太多了。

5.4 巡检制度与保留策略

最后给一套我自己的巡检清单,照着做基本不会出大乱子:

  1. 每周检查一次SYSTEM和SYSAUX表空间使用率,超过80%就要留神。
  2. 每月检查一次AUD$和FGA_LOG$的空间占用趋势,连续两个月增长超过20%就要查审计策略。
  3. 审计策略每季度复盘一次,下线了很久不用的策略,别舍不得删。
  4. 明确审计数据的保留周期,这个周期内能追溯,过了周期尽快清理。
  5. 所有清理操作记录到维护文档,日期、操作人、清理量、耗时都记下来,方便回溯。

这套巡检制度不需要额外开发什么复杂平台,一张Excel表就能管起来。但坚持执行下来,审计日志就再也不是悬在头上的定时炸弹了。

我个人在实际操作中最深的体会是:处理审计日志膨胀,最忌讳的就是“急”。越着急清理,越容易忽略锁会话、UNDO空间这些前置条件,结果把一个小问题弄成大故障,这种事每个月都在不同的生产库里上演。按部就班地查空间、查策略、选方案、挑窗口、盯监控,这套流程走一遍,30分钟到一个小时内就处理完了,而且干干净净不留尾巴。你如果能把前面那三查三看和几种清理方案吃透,以后遇到同款问题,基本都能做到心里有底,手上有活。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 5:20:50

OSPF高级特性实战指南:MTU检查、Stub区域与LSA时间控制

1. 为什么“OSPF高级特性”不是进阶考试的装饰品&#xff0c;而是现网排障的救命绳&#xff1f;刚接手某高校核心网络改造项目时&#xff0c;我遇到一个典型场景&#xff1a;三台核心路由器通过OSPF建立邻居&#xff0c;但其中一台始终卡在ExStart状态&#xff0c;邻居关系无法…

作者头像 李华
网站建设 2026/10/19 5:20:06

NTFS备用数据流(ADS)原理、检测与清理实战

1. 从一次“0字节却有内容”的排查说起1.1 一次反直觉的文件系统异常大概是两年前&#xff0c;我接手一批从旧服务器迁移出来的文件。同事很慌&#xff0c;说有个“报价清单.txt”&#xff0c;右键属性显示0字节&#xff0c;双击用文本编辑器打开&#xff0c;里面却整整齐齐写了…

作者头像 李华
网站建设 2026/10/19 5:19:46

MATLAB实现CNN-MNIST手写识别全流程:原理、调参与避坑

简介&#xff1a;面向深度学习初学者的基于卷积神经网络的手写数字识别MATLAB工程&#xff0c;以MNIST数据集为例&#xff0c;配套原理PDF文档&#xff0c;完整覆盖数据预处理、网络构建、模型训练与评估流程&#xff0c;适合机器学习课程设计、毕业设计以及智能信号处理方向参…

作者头像 李华
网站建设 2026/10/19 5:18:39

双DSP6657+FPGA K7高速信号处理板卡设计全流程

第一次在方案评审会上看到“双DSP6657 FPGA K7”这个组合的时候&#xff0c;我第一反应是&#xff1a;这年头不用两颗DSP加一片高端FPGA&#xff0c;都不好意思叫高性能信号处理板卡。直到自己真把一个这种架构的预研项目从原理图一路做到板卡跑通算法&#xff0c;才意识到这套…

作者头像 李华
网站建设 2026/10/19 5:18:32

AI编程教程项目如何被AI助手推荐?2万Star开源经验分享

1. 一个教程项目被AI助手推荐&#xff0c;意味着什么做技术教程的人都有一个朴素的愿望&#xff1a;自己写的东西能被人搜到、被人用上、被人认可。但这两年发生了一个微妙的变化——越来越多的人不再通过传统搜索引擎找答案&#xff0c;而是直接问AI助手。你问它“怎么学Pytho…

作者头像 李华