ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

报错排查实战:从数据库语法到构建运行时,编程报错的系统性解决思路

报错排查实战:从数据库语法到构建运行时,编程报错的系统性解决思路 深夜两点半我盯着屏幕上那行红色的报错一边翻着搜索引擎里零零散散的解决方案一边在笔记里写下第37条报错备忘。这个场景应该很多同行都不陌生。做开发这十年我越来越觉得报错这个东西本质上不是程序在为难你而是程序在用自己的方式告诉你我哪里不对劲了你顺着线索来找。真正折磨人的不是那行红色的英文而是你两眼一抹黑、完全没有排查思路的时刻。这篇一些报错记录不是什么高深的技术教程就是把我在实际项目里收集到的、有代表性的报错案例拆开揉碎从数据库到构建打包、从运行时报错到那些冷门到搜不到答案的犄角旮旯讲清楚每一类报错背后的逻辑链路。不管你是刚入行的新手还是已经写了好几年代码的老兵我都建议养成记录报错的习惯——你今天踩的坑百分之八十会在三个月后的另一个项目里换个马甲重新出现。1. 数据库报错的两种典型死法SQL语法与启动时序数据库相关的报错在开发里出现频率极高但大部分时候翻来覆去就是两类问题一类是SQL本身写得有问题另一类是数据库服务的启动顺序或残留状态出了问题。这两类报错的表现形式完全不同排查思路也是两套打法。1.1 MySQL 1064先把报错里的near位置读清楚MySQL的1064语法错误可能是所有数据库报错里最常被搜索的一种。就拿mysql1064报错怎么解决这个热搜来说几乎每天都有新人遇到。这类报错的提示信息长这样ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near desc limit 10 at line 1很多新手看到check the manual就直接懵了心想我总不能去翻几百页的官方文档吧。其实这个报错最有价值的信息就是near后面跟着的那一小段内容。它告诉你MySQL解析器是在哪个位置开始读不懂的。拿上面的例子来说问题就出在desc上。DESC是MySQL的保留关键字用来做降序排序或者查看表结构如果你拿它当字段名不加上反引号1064就是家常便饭。我自己的排查套路是三步走把报错里near后面的内容连同一整条SQL一起复制出来先做格式化。很多时候你写的一长串SQL在编辑器和命令行里看着没问题一旦粘到客户端工具里换行、缩进全乱了你根本看不清各个子句的边界在哪。格式化之后SELECT、FROM、WHERE、GROUP BY这些关键子句一目了然语法错误多半就暴露了。二分法删减SQL。如果你格式化之后还是看不出来就从中间开始删掉一半子句保留完整结构继续执行逐步缩小范围。我见过很多次情况是问题不在报错语句本身而是子查询里少写了一个别名或者一个逗号。检查保留字和引号。字段名跟关键字重名是最容易踩的坑另一个坑是字符串值用了双引号而MySQL的sql_mode又没开ANSI_QUOTES结果字符串被当成了标识符。这里多说一句1064报错还有个容易忽略的变体就是字符集导致的看不见的字符。比如你在Word里编辑SQL复制进来的时候带了弯引号或者不间断空格肉眼完全看不出来但MySQL就是报语法错误。遇到这种情况把SQL粘贴到能显示空白字符的编辑器里查一遍。1.2 ClickHouse 重启报错failed to flush system log already exists比起MySQL的语法错误ClickHouse重启时报的failed to flush system log already exists这种错误明显更让人头大。语法错误至少报错位置明确这种服务启动失败的错误往往意味着你连数据库都进不去排查手段极其有限。这个报错出现的典型场景是这样的服务器异常断电或者ClickHouse进程被kill -9强杀然后你重新启动服务发现起不来了日志里反复出现类似Failed to flush system log table ... already exists这样的信息。这背后是什么原因呢ClickHouse内部有一套系统日志表比如system.query_log、system.query_thread_log、system.trace_log这些它们记录了查询历史、线程信息和追踪数据。正常运行的时候ClickHouse会定期把内存里的日志数据异步刷新flush到磁盘上的系统表里。如果进程上次是异常终止的刷新操作只做了一半——元数据已经写进去了但数据还没来得及完整落盘。下次重启的时候系统尝试再次执行刷新发现那个对象已经存在于是直接抛错服务启动流程中断。这个问题的麻烦之处在于你不能像修业务数据那样直接跑一条SQL去改因为服务压根没起来没有查询接口可以用。我的处理步骤是这样的第一步找一台新机器或者先手动停下服务把整个数据目录做完整备份。这个操作虽然慢但必须有操作元数据目录的容错率很低一个rm下去可能整个库都废了。第二步进到ClickHouse的数据目录下找到metadata相关的目录定位到系统日志表的目录结构。这里需要特别留意带log字样的子目录往往就是上次flush残留的元数据。第三步在确认备份没问题的情况下把这些残留的system log相关目录改名而不是直接删除。改名的好处是如果你判断失误还能改回来而且ClickHouse重启检测不到那个已存在的对象就会自动重新初始化系统表。最后重新启动ClickHouse服务观察日志确认系统表重建成功。这类报错给到我的教训是数据库服务的优雅启停不是可有可无的仪式。kill -9解决不了一个卡死的进程时确实省事但代价是一个潜在的启动炸弹。能用clickhouse stop或systemctl stop让服务自己处理完待刷新的数据就别图快。2. 构建与打包的报错最容易被环境细节坑到如果你说数据库报错还能靠SQL功底解决那构建打包阶段的报错就完全是另外一回事了。这类报错最坑的地方在于代码在别人机器上能编译偏偏到你机器上就挂或者IDE里运行得好好的一换成命令行打包就失败。你很难说是代码的问题越来越怀疑是自己的开发环境有什么不干净的东西。2.1 IntelliJ Maven 打包报错从堆栈尾部倒着找原因Intellijmaven项目打包报错这个热搜词我太熟了几乎每个用Java做后端开发的人都遇到过。Maven打包报错的形态五花八门但有一个通用排查原则可以先记住永远从堆栈最底部开始看而不是最上面。很多新手看报错习惯从第一行看起对着最上面的[ERROR]一行研究半天。但实际上Maven的报错信息是层层包裹的最顶部往往是Maven execution failed这样的笼统描述真正有用的Caused by藏在最下面。举个例子报错信息长这样[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project demo: Compilation failure [ERROR] /path/to/xxx.java:[18,43] 程序包com.alibaba.fastjson不存在别看上面那行那么长真正告诉你答案的就是程序包com.alibaba.fastjson不存在。这说明编译ClassPath里压根没有fastjson这个依赖。这个时候去检查pom.xml多半能发现以下四种情况之一依赖坐标写错了比如groupId写成了另一个公司的。依赖声明了scopeprovided/scope或scopetest/scope结果你在主代码里引用了。父级pom里的dependencyManagement锁了版本导致依赖没有真正传递下来。本地仓库里的包损坏了Maven拉取到一半中断剩一个残缺的jar文件。针对最后一种情况还有个实用技巧如果你怀疑本地仓库有半截文件直接进~/.m2/repository找到对应路径删掉当前目录然后重新执行mvn clean package让Maven重新下载。别手动去改jar包那是越弄越乱。我还遇到过一种编码相关的Maven打包报错特别隐蔽代码里有中文注释本地IDE把文件按GBK保存Linux服务器上构建时默认读UTF-8结果中文注释变成了乱码导致编译失败。排查方式是在pom.xml里显式声明project.build.sourceEncodingUTF-8/project.build.sourceEncoding同时把编辑器里文件的编码统一切换到UTF-8一劳永逸。2.2 Tauri Windowslink.exe not found 的修复链路这两年桌面应用开发里Tauri很火但Tauri在Windows上的入门报错也格外劝退新人。tauri windows报错link.exe not found这个搜索词基本上每个Tauri新手都会搜一遍。我第一次遇到这个报错的时候也很困惑我电脑上明明装了Visual Studio Code能写Rust代码怎么cargo tauri dev跑起来就找不到link.exe呢这里要先把一个概念说清楚Tauri的构建链在Windows上依赖的是MSVC工具链也就是微软的C编译器和链接器而不是你写Rust代码时用的编辑器或编译器前端。link.exe是MSVC的链接器负责把编译出来的目标文件链接成最终的可执行文件。你光装了Rust的MSVC工具链也就是rustup默认安装的stable-x86_64-pc-windows-msvc但没有装Visual Studio Build Tools那么link.exe就根本不存在cargo编译到链接那一步就直接罢工了。解决链路其实很清晰打开Visual Studio Installer找到使用C的桌面开发这个工作负载勾选安装。这里面要重点确认包含MSVC v143或对应版本的C编译工具和Windows 10/11 SDK。只装这两个组件别贪多。安装完成后link.exe通常会被放在类似C:\Program Files\Microsoft Visual Studio\2022\BuildTools\VC\Tools\MSVC\14.38.33130\bin\Hostx64\x64这样的路径下。重点来了装完Build Tools之后要关掉当前所有的终端窗口再重新打开。因为环境变量是在安装过程中注入的已打开的终端窗口不会自动刷新环境变量你不重启终端照样找不到link.exe。这个细节我当初折腾了一下午才反应过来。还有一个容易踩的坑是系统里同时装了MinGW或Git自带的Unix工具链PATH环境变量里它们的路径排在MSVC前面。有些工具在查找link.exe的时候会优先命中Git带的那个链接器结果因为版本不兼容又报新错。解决办法是调整PATH顺序或者干脆在Tauri项目用的终端里先确认where link.exe的输出到底指向哪里。顺便说一句遇到link.exe not found的时候不要一上来就重装Rust。网络上很多答案会误导你去改Rust的工具链但实际上你的Rust环境大概率是好的问题只出在缺了微软的C工具集。3. 运行时报错不是代码错了是环境、权限和编码在捣乱编译通过了、打包成功了的项目不代表就万事大吉了。运行阶段冒出来的报错往往最让人抓狂因为代码看起来哪哪都正常但程序就是跑不起来。这类报错我总结下来大部分根子不在逻辑层面而在环境、权限和编码这三个隐形因素上。3.1 MediaRecorder.start() 的 start failed 排查安卓开发里用MediaRecorder录制视频几乎每个人都会遇到这个报错java.lang.RuntimeException: start failed.。关键这个报错异常信息极度吝啬就一句话不告诉你具体是哪一步配置出了问题。我调试这个问题时把Android官方文档反复翻了好几遍最后梳理出了一套排查顺序。先说一下MediaRecorder的使用流程很多人前脚刚new完对象后脚就直接调start()然后抱怨怎么失败了。MediaRecorder是一个状态机你必须按固定顺序完成配置它才愿意开始工作依次调用setAudioSource和setVideoSource指定音视频源。调用setOutputFormat设置输出封装格式比如MPEG_4。调用setOutputFile指定输出文件路径。这一步特别容易翻车你给的文件路径父目录如果不存在start阶段就是会失败——系统不会自动帮你创建目录。设置音视频编码器和参数setAudioEncoder、setVideoEncoder缺一个都会在start阶段报错。调prepare()如果prepare抛异常说明前面的配置还有问题。最后再调start()。如果你全部配置顺序都正确了仍然报start failed那就需要检查运行环境了。常见的有这么几类权限问题Android 6.0以上需要运行时动态申请RECORD_AUDIO和CAMERA权限你只在Manifest里声明了但没在代码里请求start照样失败。文件写入问题输出路径在外部存储时需要考虑WRITE_EXTERNAL_STORAGE权限在应用私有目录时目录要保证存在且可写。摄像头被占用如果你在同一个Activity里先打开了Camera预览又创建了一个新的MediaRecorder去录制而没有release()掉CameraMediaRecorder无法获得摄像头资源也会直接抛start failed。我的经验是遇到这类运行时报错先别怀疑代码逻辑按编码器配置 文件目录 权限 摄像头占用的顺序逐个排查通常五分钟内能找到答案。千万不要在start前后加一堆try-catch只打印日志而不去确认上述前置条件。3.2 VSCode运行Java乱码一半是编码一半是终端VSCode里运行Java程序控制台输出中文全是乱码这个问题搜VSCode运行java报错乱码能找到一大批同病相怜的人。乱码的本质就四个字字符集不对。Java源码文件在VSCode里默认按UTF-8保存但Windows系统控制台cmd或者Windows Terminal的某些配置默认用的是GBK编码两边对不上中文就成了一堆看到就头疼的符号。这里面有几个层面要分别处理源码编译层面如果你的Java文件里有中文运行javac编译时如果不指定编码javac会用平台默认编码Windows上是GBK去读取UTF-8的源文件这时候中文注释就会变成乱码严重的话直接编译报错。解决办法是编译命令加上-encoding UTF-8或者在Maven的pom.xml里把project.build.sourceEncoding设置为UTF-8。运行输出层面VSCode的终端输出中文乱码通常是因为Java进程输出的字节流是UTF-8而终端按GBK解码显示。需要在VSCode的settings.json里做两件事一个是把terminal.integrated.profiles.windows配置的默认终端Shell设置成支持UTF-8另一个是在java.debug.settings相关的配置里把控制台输出编码统一处理。一个快速自检技巧在Java代码里打印System.getProperty(file.encoding)看看运行环境拿到的默认编码是什么。如果显示的是GBK而你的源码是UTF-8那乱码就是板上钉钉的事。这里我强烈建议大家统一一个原则源文件、编译参数、运行终端三者的编码必须一致。我自己在Windows上做Java开发一律把工作区编码设为UTF-8编译参数强制指定UTF-8终端通过VSCode设置改成UTF-8三管齐下之后乱码再也没出现过。3.3 一个常见的JavaScript运行时错误undefined is not a functionjavascript运行时报错这个热搜词覆盖的范围太广了市面上九成的JavaScript运行时错误都能让人当场血压升高。我挑一个最典型的来讲TypeError: xxx is not a function。这类报错的信息直译就是你调用的这个东西不是函数程序员的第一反应通常是不可能啊我明明定义过这个函数。我排查过很多次这种问题发现它出现的原因不外乎三类变量覆盖你在某个作用域里用var声明了一个与外部函数同名的变量赋值成了数字或字符串导致后续调用时拿到的不是原来的函数。异步时序错位你从接口异步获取一个对象这个对象里有个方法但在接口响应回来之前你就调用了这个方法。此时xxx还是undefined你强行调用它自然就报undefined is not a function。导入导出不匹配ESModule里你import { getData }导入但实际上导出的是export default { getData }拿到的就是一个对象不是函数。这类错误在TypeScript里还能靠类型检查拦住在纯JavaScript里就只能靠运行时报错来发现。排查建议就一条在报错那一行前面加console.log打印出这个变量的类型。typeof输出清清楚楚如果你看到undefined那就是异步还没回来如果是number那就是变量被覆盖。定位到根因再动手修别看着报错就瞎猜。4. 冷门但值得记一笔的报错有些报错一年见不了几次但一旦遇到搜索引擎上的有效答案寥寥无几那种全世界只剩我一个人的孤独感写过代码的人都懂。这里我挑几个近期收集到的小众报错不一定全面但每一条都是我实际折腾出结果来的。4.1 NVIDIA 屏蔽ECC当GPU计算卡在硬件层面报警NVIDIA 屏蔽ecc报错这个热词一看就是玩深度学习训练的人搜的。我自己在服务器上跑模型训练时也遇到过nvidia-smi里显示了ECC相关的错误信息服务器本身的日志也弹了硬件报错提示。先普及一个概念ECC是显存错误检查和纠正机制它能在数据读取时检测到单比特错误并自动纠正对长时间跑大规模计算的场景非常重要。但它也不是没有代价——开启ECC会占用一部分显存带宽和容量性能有少许损耗。所以有些做深度学习训练的同学为了压榨出那一点性能会选择屏蔽ECC也就是关闭这个纠错功能。如果真的决定要关命令很直接用管理员权限执行nvidia-smi -e 0这个命令的作用是禁用当前GPU的ECC支持。执行之后nvidia-smi再查看ECC相关的状态就会变成Disabled。想恢复的话把0改成1再执行一次就是重新启用。但如果你问我的个人看法我要泼一盆冷水。ECC报错的本质是硬件已经开始出现异常了它是在提醒你显存可能有坏块或正在劣化。屏蔽ECC只是让系统不再报告这个异常相当于把仪表盘的警报灯线剪了问题本身还在那。如果是测试机自己折腾着玩那关了就关了如果是生产环境或者正在跑正式训练任务正确的做法是记录GPU的序列号和报错时间走设备售后检测流程而不是急着关闭这个功能。我见过有人关了ECC之后继续训练结果模型训练到一半损失值突然发散最后排查发现就是显存数据被静默写错了这类问题造成的浪费比那一点性能损耗大得多。4.2 gloo报错分布式通信库在找队友时翻车gloo是Facebook开源的分布式通信库很多AI框架在分布式训练时拿它来做多机多卡之间的数据交换。gloo报错应该如何改搜索量虽然不高但提问的人基本都是卡在分布式训练任务起不来。gloo最常见的报错有两大类。第一类是关于网络接口的典型提示是找不到可用的网卡或选择了错误的接口。很多服务器有多个网卡管理口、业务口、高速互联口混在一起gloo不知道选哪个的时候就会报错。这类问题的解决思路是显式指定网卡通过环境变量GLOO_SOCKET_IFNAME强制它使用你想要的那块网卡比如export GLOO_SOCKET_IFNAMEeth0第二类是节点间通信超时或握手失败。这类问题的排查思路要顺着链路一层一层看先确认节点间的端口能通可以用nc -zv挨个测试常见端口再检查防火墙规则有没有放行gloo用的通信端口最后确认共享内存目录/dev/shm有足够的空间。遇到过容器环境里/dev/shm默认只有64MB跑着跑着gloo直接报错的案例——把共享内存调大到几个GB就解决了。4.3 报错信息本身可能是线索一个CTF查询系统的怪报错最后聊一个比较有意思的案例一个CTF比赛里的小查询系统功能非常简单输入ID就能查询到对应信息。但选手们测试时发现有些输入会让系统返回一个奇怪的报错这个报错信息里不仅带出了部分SQL查询语句还泄露了服务器上文件路径的细节。这个场景放到安全圈很好理解报错信息是信息泄露的经典入口。很多开发者为了方便排查会把数据库操作的详细错误直接原样抛给前端用户。用户输入的内容如果被直接拼接进了SQL语句一旦输入了特殊字符SQL语法就被破坏数据库的报错信息里通常会包含查询语句片段。这些片段对攻击者来说就是深入分析系统结构的线索。CTF题目会故意保留这种报错来作为解题突破口而真实业务系统里这就属于严重的安全隐患了。这个案例给到开发侧的启示其实很简单生产环境的报错信息必须做脱敏和收敛。前端用户能看到的永远应该是系统繁忙请稍后再试这类无害提示而完整的堆栈信息应该写入服务端日志并配合日志采集系统做监控告警而不是直接吐给浏览器页面。判断一个系统的成熟度有时候就看你把报错信息藏得多好。做技术这些年我一直保持着记报错笔记的习惯。每解决一个问题就顺手把报错原文、根因分析和解决命令写进一个专门的文档里按关键词打标签方便日后搜索。刚开始觉得这事情很花时间写多了才发现这是效率回报最高的一项投资——很多时候你以为又在面对一个新技术难题翻出笔记一看三年前就解决过一模一样的坑。如果你也是一线开发者真心建议从今天开始给报错建档。它不会让你少遇到问题但会让你在遇到问题时知道该去哪里找答案。
返回列表