1. 先把概念理清:重启数据库到底重启了什么
1.1 实例和数据库,是两套东西
不少人嘴里的“重启 Oracle 数据库”,实际操作就是敲一句 shutdown immediate 再加一句 startup,动作很快,但如果问他这一步到底关掉了什么、又打开了什么,往往答不上来。真遇到启动报错,答不上来的人就只能干瞪眼。所以这个话题得从最底层说起:Oracle 的“实例”(Instance)和“数据库”(Database)是两个可以独立描述的实体。
实例是内存加进程,说直白点就是服务器上那一组后台进程(DBWn、LGWR、CKPT、SMON、PMON、ARCn 等)加上一块共享内存(SGA,里面装着 buffer cache、shared pool、redo log buffer 这些区域)。数据库是磁盘上的文件集合,包括数据文件、控制文件、在线重做日志、参数文件。实例活着的时候把数据库挂上去,用户才能读写;实例一挂,磁盘上那些文件还在,但没人给它们当“大脑”了。
这个区分为什么重要?因为 shutdown 是把实例从内存里拆掉,startup 是重新建实例再把文件挂上去。“重启”这两个字掩盖了一个事实:中途有一段时间你的数据库是完全不可用的,而且重新挂载的过程里 Oracle 要做一致性检查。理解这一点,后面所有关于“用哪种关闭方式”“为什么要等”的讨论才有落脚点。你如果是刚接手一套库的新人,先记住一句话:重启不是“刷新一下”,是“停业再开业”。
1.2 四种关闭方式,差别不只是快慢
SQL*Plus 里能敲的关闭方式有四种:normal、transactional、immediate、abort。很多人只记得 immediate 和 abort,其实前两种在某些场景更有价值。我把它们的真实行为拆开讲,这里的行为差异直接决定了你的业务会感受到多大的抖动。
| 关闭方式 | 是否允许新连接 | 对未提交事务的处理 | 是否做检查点 | 耗时 | 重启后是否需要实例恢复 |
|---|---|---|---|---|---|
| SHUTDOWN NORMAL | 不允许 | 等用户自己提交或回滚 | 做 | 最长,可能永远等不到 | 否 |
| SHUTDOWN TRANSACTIONAL | 不允许 | 等当前事务结束,随后断开会话 | 做 | 中等 | 否 |
| SHUTDOWN IMMEDIATE | 不允许 | 立即回滚未提交事务,断开所有会话 | 做 | 短 | 否 |
| SHUTDOWN ABORT | 立即切断 | 不回滚,直接丢弃 | 不做 | 最短 | 是 |
这张表最容易被忽略的一行是最后一列。很多人以为 abort 之后 startup 会秒开,实际上 startup 那一步 SMON 要做实例恢复:前滚(把在线日志里已提交但还没写进数据文件的变更重放一遍),再回滚(把没提交的事务撤掉)。库越大、abort 时脏块越多、日志越多,这个恢复窗口越长。我见过一个几百 GB 的库,abort 之后 startup 卡了十几分钟,DBA 在屏幕前面以为卡死了,其实人家在老老实实做恢复。
所以选择逻辑很清楚:能 immediate 就别 abort,除非 immediate 卡住不动了。normal 和 transactional 在实际运维里用得少,但不是没有用武之地:比如你要在维护窗口做一次“优雅退出”,业务方承诺十分钟内自己断连接,用 transactional 比 immediate 更温和,至少不会让一堆正在跑批的任务全部回滚重来。
1.3 什么时候该重启,什么时候重启只是心理安慰
这一节是我最想说的。有些团队形成了条件反射:系统慢了?重启。连接数满了?重启。报了个 ORA- 错误?重启。重启确实能“解决”一部分问题,但它解决的往往不是问题本身,而是问题的症状,过两天原样复发,而且每次都多埋一层隐患。
真正需要重启的典型场景,我归成三类。第一类是配置或补丁生效:改了静态参数(比如 sga_target、processes、db_block_size)、打了数据库补丁、改了 spfile 里必须重启才读的参数。这类没办法,只能重启。第二类是环境级故障:实例 hang 住、后台进程异常、共享内存被外部因素破坏、归档空间满导致挂起且在线清理不动。第三类是被动因素:操作系统要打内核补丁、主机要迁移、存储要换。
反过来,那些不该靠重启解决的问题也得说清楚:shared pool 碎片严重,重启确实能清空,但真正的解法是绑定变量、调整游标共享;临时表空间爆了,重启能清掉,但根因是排序没走索引或者 PGA 配置不合理;锁等待,重启能强拆,但那是拿全库可用性换一个会话。重启是外科手术,不是感冒药。把这句话记住,你能避开后面一大半的坑。
2. 重启前的准备:这部分比重启本身更值钱
2.1 动手前必须过一遍的检查清单
我个人的习惯是:只要不是救火,任何一次重启之前都要在会话里跑一遍检查,确认没有“正在发生的重要事情”被我一刀切掉。这些检查加起来不到两分钟,但能避免很多事后追悔。下面这套是多年沉淀下来最精简的版本。
-- 1. 确认当前实例和打开模式 select instance_name, status, database_status from v$instance; select name, open_mode, log_mode from v$database; -- 2. 有没有长事务在跑 select s.sid, s.serial#, s.username, s.machine, t.used_urec, t.used_ublk, t.start_time from v$transaction t join v$session s on t.ses_addr = s.saddr order by t.start_time; -- 3. 有没有在线备份正在进行 select * from v$backup where status = 'ACTIVE'; -- 4. 正在跑的作业 select job_name, session_id, running_instance, elapsed_time from dba_scheduler_running_jobs; -- 5. 归档空间余量(这一项经常被人忘) select * from v$recovery_file_dest; select * from v$flash_recovery_area_usage;第 5 项我特意放在最后强调,因为它是重启失败最常见的原因之一。归档区满了、archivelog 写不进去,数据库会挂起;这时候你重启,startup 阶段 mount 能过,open 的时候因为要做恢复需要读日志、写新日志,反而更容易卡住。重启之前先确认归档有空间,能省掉一场事故。
还有一个检查是很多人忽略的:select count(*) from v$session where type='USER';。这个数字本身不说明什么,但如果你平时知道这个库的基线是 200 个连接,现在忽然是 1200 个,那说明有应用在反复重连或者连接池泄漏——这时候重启只是把问题延后半小时。先看一眼,心里有数。
2.2 备份和回退预案:不是形式主义
重启本身不会损坏数据(除非你玩的是一些非常规手段),但它会把你带到“必须顺利启动”的单行道上。所以我坚持两条规矩:第一,重启前确认有可用的最近备份,并且知道恢复路径怎么走;第二,确认参数文件的当前状态是已知的。
参数文件这条尤其容易翻车。很多人的库用的是 spfile,改参数用alter system set ... scope=both,改完没记;等到某天改错了参数导致起不来,才发现自己手上没有一份“改之前”的参数文件快照。我的做法是:重启前无条件导出一份当前参数,落到一个带日期的文本里。
create pfile='/tmp/pfile_before_restart_20240115.ora' from spfile;起不来的时候,startup pfile='/tmp/pfile_before_restart_20240115.ora'这一句就是救命稻草。同理,控制文件的位置、在线日志成员的数量、归档路径,这些都随手记录一下,比事后翻文档快得多。
2.3 时间窗、通知和“谁来盯”
技术准备做完了,还有组织层面的事。我一直觉得这块被严重低估。一个库要停 5 分钟,涉及的应用方可能有三四个团队,任何一个没通知到,第二天就会有人跑来问“刚才是不是你们把库停了,我们这边报了一堆错”。
我的做法是把信息压缩成三行发出去:停多久、影响哪些连接方式(是不是所有应用都断、只读库要不要停)、出问题找谁。时间窗尽量贴着业务低峰,但要注意“低峰”不等于“没人”,很多批处理调度恰好安排在凌晨,你一刀切下去,跑到一半的 ETL 全废,第二天数据对不上,查原因能查一整天。
还有一个实操细节:重启期间要有人在场盯着。我遇到过 startup 之后 open 阶段卡住,也遇到过监听起来了但 TNS 解析不对。这些都不是“敲完命令就走”能发现的。哪怕全程只有三分钟,人也要在。
3. 单实例重启的标准动作与细节
3.1 关闭阶段:环境变量先对,命令后敲
关闭前第一件事不是敲命令,是确认你连的是哪个实例。服务器上装了两三个 ORACLE_HOME、跑了不止一个实例的情况太常见了,echo $ORACLE_SID和echo $ORACLE_HOME这两句必须先敲。我见过在 A 库的窗口里对 B 库下了 shutdown,原因是之前 su 到 oracle 用户时环境变量带错了。
标准做法是用 oracle 用户登录,让 profile 把环境带起来:
su - oracle echo $ORACLE_SID echo $ORACLE_HOME echo $PATH如果环境变量不对,手工修正:
export ORACLE_SID=orcl export ORACLE_HOME=/u01/app/oracle/product/19.0.0/dbhome_1 export PATH=$ORACLE_HOME/bin:$PATH export LD_LIBRARY_PATH=$ORACLE_HOME/lib:$LD_LIBRARY_PATH确认无误后再进 SQL*Plus。关闭前先做一个“软处理”:把作业队列停掉,避免刚关一半又有 job 爬起来连数据库。
alter system set job_queue_processes = 0 scope=memory; show parameter job_queue_processes;scope=memory的好处是不动 spfile,重启后自动恢复原值,省得你还要记得改回来。这一步做完,再执行关闭:
shutdown immediate;如果这条命令敲下去几分钟没返回,不要急着重开一个窗口再敲一遍(那样很可能给你返回 ORA-01013 之类让人更慌的错误)。到另一个会话里看v$session里还有没有活动会话、看 alert log 最后几行在干什么。多数情况下是 SMON 在回滚一个巨大的事务,或者 DBWn 在刷脏块,等着就行。
3.2 启动阶段:nomount、mount、open 三个台阶
startup一条命令就把三件事做完了,但真出问题的时候,你得知道它卡在哪一级。nomount 阶段读的是参数文件,实例建立、SGA 分配、后台进程拉起,这时候还没有任何“数据库”的概念,能查v$instance但查不了v$database。mount 阶段读的是控制文件,Oracle 从控制文件里知道了数据文件和在线日志在哪、当前日志序列号是多少。open 阶段才去打开数据文件和日志,如果上次不是干净关闭,这里就要做实例恢复。
为什么值得记这个?因为排错时它是分诊台。起不来在 nomount,说明是参数文件、内存分配、进程数、权限的问题;卡在 mount,说明是控制文件路径、权限、副本损坏;卡在 open,多半是数据文件或日志文件的问题。分级定位比乱翻日志高效十倍。
需要分步走的时候,就拆开敲:
startup nomount; -- 检查实例是否起来 select status from v$instance; startup mount; -- 检查控制文件是否读到了 select name, controlfile_type from v$controlfile; select name, open_mode from v$database; alter database open;分步走还有一个额外好处:mount 状态是做很多维护操作的唯一窗口,比如改归档模式、rename 数据文件、恢复控制文件。所以不要觉得分步是麻烦,它是把可控性握在手里。
3.3 监听:数据库起来了不等于业务能连
这一条坑过太多人。数据库 start 成功、v$instance显示 OPEN,业务还是报连接失败,原因就在监听。数据库的启动和监听器的启动是两条独立的线,监听挂了,本地 sqlplus/ as sysdba照样能连,但走 TCP 的应用全连不上。
重启后确认监听:
lsnrctl status如果监听没起来:
lsnrctl start如果监听起来了但服务没注册(status 里看不到你的 service_name),可以手动注册一下:
alter system register;我还遇到过一种情况:监听配置里写的是静态注册,实例起来后服务状态是 READY 而不是 BLOCKED,本地看着一切正常,但客户端连上之后很快断开。这类问题多半要去看listener.ora里的 GLOBAL_DBNAME 和 SID_NAME 是否和实际匹配。重启完顺手看一眼监听日志,比等业务打电话过来强。
另外提醒一个细节:如果你在 Linux 上是通过 systemd 或者某种自启脚本管理数据库的,重启主机和重启数据库是两件事,顺序别搞反。先起监听再起库,或者先起库再起监听都可以,但两者都要起。有些环境把监听做成了独立服务,重启完数据库忘了起监听,白白多停机十分钟。
3.4 重启后的必查清单
数据库 open 之后,我会按下面的顺序过一遍。这个顺序不是随便排的,是从“最容易出问题”到“最容易忽略”排下来的。
| 检查项 | 命令或方法 | 关注点 |
|---|---|---|
| 实例状态 | select status from v$instance; | 必须是 OPEN |
| 数据库模式 | select open_mode, log_mode from v$database; | READ WRITE / ARCHIVELOG |
| 告警日志 | alert log 尾部 200 行 | 有没有 ORA- 错误、块损坏 |
| 无效对象 | select count(*) from dba_objects where status='INVALID'; | 数量是否异常增长 |
| 作业队列 | show parameter job_queue_processes; | 是否恢复成原值 |
| 监听状态 | lsnrctl status | 服务是否注册、端口是否正确 |
| 连接测试 | 用应用账号连一次并跑条查询 | 别用 sys 测完就算完 |
| 归档空间 | select * from v$recovery_file_dest; | 有没有被重启过程写满 |
| 备份作业 | 看调度平台或dba_scheduler_jobs | 明天凌晨的备份还在不在 |
特别说两条。无效对象数量这条,正常重启后应该是 0,或者和重启前一致。如果突然多出几百个 INVALID,说明有依赖对象在启动过程中没编译上,后面应用一调用就报 ORA-04063,必须马上处理。归档空间这条也很关键,重启过程中如果触发了大量日志切换,归档区可能被迅速填满。我吃过一次亏,重启后没看,第二天凌晨归档满了把库挂起,一晚上批处理全废。
4. ABORT 的代价:实例恢复到底在干什么
4.1 ABORT 之后 Oracle 在后台忙什么
shutdown abort是所有 DBA 都绕不开的一个命令。它的行为是:直接终止所有后台进程和服务器进程,不写检查点,不回滚事务,不更新数据文件头,也不清理临时段。内存里的东西说没就没。很多人以为这就完了,其实代价全在下次启动的时候还。
下次startup到 open 阶段,Oracle 会先读控制文件里记录的最后一个检查点信息,然后做两件事:前滚(roll forward)和回滚(roll back)。前滚是把在线日志里从检查点之后的所有变更重做一遍,包括已提交的和未提交的;回滚是把那些未提交的事务撤销掉。整个过程由 SMON 主导,进度可以在 alert log 里看到类似的记录,也可以在v$session里看到 SMON 的等待事件。
这里有个细节值得知道:恢复的数据量取决于检查点之后产生了多少 redo。如果库很忙、日志切得频繁、abort 之前刚刚切过一次日志,那需要前滚的量就小,恢复很快;如果库闲、日志半天切一次,看着没多少操作,实际上从检查点到 abort 之间的 redo 可能横跨好几组日志,恢复就慢。所以“库不忙所以 abort 一下没关系”这个推论是不成立的。
4.2 什么情况下必须用 ABORT
我自己的原则是:abort 是最后手段,但有些场景下它确实是唯一选择。第一种是immediate 卡死不返回,等了几分钟 alert log 一动不动,说明有东西在阻塞关闭流程(常见的是某个进程在等 IO,或者有会话在持有一把关键闩),这时候只能 abort。第二种是实例已经异常,比如后台进程挂掉、共享内存被破坏、大量 ORA-00600,这种情况下 immediate 大概率也执行不了。
第三种比较微妙:需要极短的停机窗口。比如某个库的 immediate 要跑三分钟,而你的窗口只有一分钟,那用 abort 换更快的关闭,代价是下次启动多花时间做恢复——注意,总时间不一定省,只是把时间从“关”挪到了“开”。所以这个取舍要算清楚:如果业务方对“关”的时间敏感(比如要赶紧让出主机做硬件维护),那 abort 合理;如果对“开”的时间敏感(比如早上八点必须对外服务),那 immediate 更稳妥。
还有一种情况可以用 abort:反复重试都关不掉。我遇到过一次,immediate 敲了三遍,每次都返回一点进展然后就停住,最后 abort 解决,启动的时候做了七分钟实例恢复,日志里能看到 SMON 一遍遍前滚。事后查根因是一个超大事务在回滚时卡在了某个 IO 上。
4.3 哪些参数和操作会让恢复变慢
如果你经常在维护窗口里用 abort,有几个东西值得关注。fast_start_mt_target和fast_start_parallel_rollback会影响恢复阶段的并行度。前者的作用是限制崩溃恢复期间脏块刷新的速率,避免恢复刚做完又把 IO 打爆;后者控制回滚的并行度,LOW、HIGH、FALSE三档,大事务多的库设成HIGH能明显缩短回滚时间。
show parameter fast_start_mt_target; show parameter fast_start_parallel_rollback; alter system set fast_start_parallel_rollback = HIGH scope=both;另外,log_checkpoint_timeout、log_checkpoint_interval这类参数在现代版本里一般交给自动检查点管理,不建议手工调。真正需要关心的是归档区空间:恢复过程可能要读归档日志(如果在线日志不够用),归档文件缺失或者被删了,恢复直接失败,这时候就只能上备份恢复了,那已经是另一个量级的事故。
5. RAC 和多租户环境:重启的姿势完全不一样
5.1 单实例那套命令,在 RAC 上不一定好使
在 RAC 环境里,shutdown immediate只关当前节点上的实例,其他节点的实例还在跑,数据库整体对外仍然是可用的。这是好事也是坑:好事是你可以做滚动重启,业务感知很小;坑是如果你以为“关掉这个节点就停了”,结果另一个节点还在接业务,你会对“库到底停没停”产生误判。
RAC 上我更推荐用集群层的工具,因为它知道实例和资源的依赖关系:
# 查看集群资源状态 crsctl stat res -t # 停止整个数据库(所有实例) srvctl stop database -d orcl -o immediate # 启动整个数据库 srvctl start database -d orcl # 只重启某一个实例 srvctl stop instance -d orcl -i orcl1 -o immediate srvctl start instance -d orcl -i orcl1用 srvctl 的好处是它会顺带处理服务(service)的迁移。如果你只用 SQL*Plus 关实例,服务可能会短暂地“悬空”,连接池里的连接全打到已关闭的节点上,表现为应用端一批连接超时。用 srvctl 并配合-o immediate,服务会按配置的故障转移策略切换,抖动小得多。
5.2 多租户:CDB 关了,PDB 不一定跟着“干净”地关
12c 之后多租户是主流,很多人的库是 CDB 加若干 PDB。重启 CDB 的时候,PDB 会跟着一起关、一起开,但这里有个细节:PDB 的打开状态在重启后不一定是你期望的状态,除非你提前保存过。
-- 重启前查看各 PDB 状态 select con_id, name, open_mode from v$pdbs; -- 保存打开状态,重启后自动恢复 alter pluggable database all save state;save state这个动作会把当前的打开状态记录到 CDB 的系统表里,重启后数据库会自动把上次保存的 PDB 拉到对应状态。没做这个动作的库,重启后 PDB 可能停在 MOUNTED,应用连上来直接报“服务不可用”,而 CDB 本身是好好的,很容易看走眼。
如果只想重启某个 PDB 而不动整个 CDB,那就灵活多了:
alter pluggable database pdb1 close immediate; alter pluggable database pdb1 open;PDB 级别的关闭不影响其他 PDB,这是多租户最大的运维红利之一。我给一个测试库做参数调整时,基本只在 PDB 层操作,CDB 一年到头都不动。把影响面缩到最小,是运维的基本美德。
5.3 RAC 滚动重启的顺序和节奏
如果要做 RAC 整体的补丁级重启,滚动是唯一合理的做法:一次只动一个节点,等它完全起来、服务恢复、连接稳定了,再动下一个。中间那个“等服务恢复”的阶段最容易被忽视——实例起来了不代表连接池已经重新连上来了,很多应用连接池的探活间隔是 30 秒到几分钟,你要是紧接着关第二个节点,可能正好赶上应用还在往第一个节点重连,瞬间抖动放大。
我的节奏是:动节点一,起来后跑一次连接测试,观察三到五分钟的服务端会话数,确认连接确实爬回来了,再动节点二。看似慢,实际上比“两个节点一起关然后都起不来”快得多。
6. 重启解决不了的问题,以及那些年踩过的坑
6.1 三类“重启后会变慢”的隐形副作用
这一段是我认为最有价值的部分,因为几乎所有文档都不会提。重启之后,数据库看起来一切正常,但性能曲线会有一段明显的异常,原因有三个。
第一是共享池被清空导致的硬解析风暴。重启后 shared pool 是空的,所有 SQL 都要重新解析、重新生成执行计划。一个平时稳定跑几百条 SQL 的应用,重启瞬间可能变成每分钟几千次硬解析,CPU 直接飙到 90% 以上,持续十几分钟到半小时才慢慢降下来。缓解办法是在业务低峰期重启,或者用dbms_shared_pool提前把核心游标固定住(对 11g 及以上,可以考虑ALTER SYSTEM SET cursor_sharing配合应用侧绑定变量改造,但更根本的还是把 SQL 写成绑定变量的形式)。
第二是 buffer cache 冷启动带来的物理读暴涨。重启后内存里没有任何缓存块,所有查询都要从磁盘读。平时一次查询逻辑读十万、物理读几百,重启后物理读可能变成几万,IO 直接打满。如果底层存储本身余量不大,这段时间的业务响应会很难看。能让业务方提前知道“重启后半小时内会慢一点”,比事后解释省事得多。
第三是序列(sequence)缓存丢失导致的跳号。用了cache 20或者cache 1000的序列,重启后内存里的缓存值丢失,下次取值会直接跳到下一个缓存段。如果业务对连续性有要求(比如单号),这就成了一次数据质量事件。这个副作用无法避免,只能在设计阶段就决定:要么容忍跳号,要么不用 cache(性能代价大),要么用自定义发号服务。
6.2 常见问题速查表
下面这张表是我这些年处理重启相关问题攒下来的,按“症状”查比较快。
| 报错或症状 | 大概率原因 | 处理方向 |
|---|---|---|
| ORA-01034: ORACLE not available | 实例没起来,或环境变量指向了别的 SID | 确认$ORACLE_SID,startup |
| ORA-01081: already-running ORACLE | 实例已在运行 | 查v$instance,别重复启动 |
| ORA-27101: shared memory realm does not exist | 实例已挂但内存段残留,或 SID 不对 | 确认 SID,必要时清理残留段后重启 |
| ORA-03113: end-of-file on communication channel | 服务器进程被异常终止,常见于启动失败 | 看 alert log 尾部,找第一个 ORA- 错误 |
| startup 卡在 mount 不动 | 控制文件损坏或不可读 | 检查控制文件路径、权限、副本 |
| startup 卡在 open 很久 | 正在做实例恢复 | 等,看 SMON 等待事件和 alert log |
| 库起来了但业务连不上 | 监听未启动或服务未注册 | lsnrctl status、alter system register |
| 重启后大量 ORA-04063 | 依赖对象 INVALID | 查dba_objects,重新编译 |
| 重启后 CPU 飙高 | 硬解析风暴 | 观察,必要时临时调大 shared pool |
| 重启后归档区告警 | 日志切换集中,归档暴涨 | 清理过期归档,或调大 FRA |
表里有两条想单独展开。ORA-03113是重启失败时最常见的一类表象,它本身不是根因,只是客户端看到的“连接断了”。真正的根因一定在服务端的 alert log 里,往前翻,找到第一个出现的 ORA- 错误,那才是要处理的东西。很多新手盯着 03113 查半天,方向完全错。
控制文件相关的问题,要记住一个原则:只要还有一份完好的控制文件副本,就还有救。startup nomount起来后用alter system set control_files=...指定可用副本,再alter database mount,前面的功夫就都不白费。所以控制文件多路复用是底线配置,不要图省事只留一份。
6.3 几条不太"正统"但很实用的经验
说说平时文档里看不到的东西。
第一,重启前先在测试库跑一遍。尤其是要改参数的重启,测试库上跑一遍,看看启动时间、有没有报错、应用连不连得上。测试库的 SGA 小、数据量小,时间没法直接类比,但“能不能起来”这个结论是可靠的。
第二,记录每一次重启的耗时。我会在维护记录里写清楚:关闭用了多久、启动用了多久、中间实例恢复用了多久。这个数据在规划下一次窗口时极其有用。一个库重启要 20 分钟,你只申请 15 分钟窗口,那不是勇敢,是给自己找麻烦。
第三,别在周五下午做重启。这条听起来像玩笑,但背后的逻辑很正经:出问题的时候,能帮你一起看的人、能联系上的应用方、能批紧急变更的人,都在。周五下午动手,一旦卡住,你面对的是一整个周末的孤立无援。
第四,重启后至少观察半小时再撤。数据库 OPEN 只是起点。连接数有没有爬回来、CPU 曲线有没有回落、IO 有没有降下去、告警日志有没有新的报错,这些都需要时间窗口才能看出来。敲完 startup 就关掉终端走人,是最容易出事的操作习惯。
第五,把重启这件事自动化,但保留手动确认。我现在的做法是写一个脚本,把前面那些检查、关闭、启动、验证串起来,但关键步骤之间加一个“确认”提示,人必须敲一下回车才继续。全自动没人看着的重启,失败了往往比手动更惨,因为没有人知道它卡在哪里。
最后分享一个我自己的判断标准:如果同一个库在三个月内因为“性能问题”重启了两次以上,那它需要的不是重启,是一次正经的根因分析。重启是最容易做的动作,也最容易掩盖真正的问题。真正做过几年 DBA 的人,对shutdown这个命令是有敬畏心的——它不是“刷新”,它是把一整套正在运行的系统连根拔起再种回去,每一次都值得认真对待。