引语:线上运行的真实环境总是变幻莫测,明明你在本地测试的时候各种情况都是OK得不要不要的,也许你还在为自己某个地方炫酷的效果以及神奇的设计感到激动不已!但是,到线上以后,他就会偶尔跟抽风一样的跟你say Hello,World!然后会有人跟你说,这里出问题了,那里出问题了!反应往往是这样,“不可能!”,“妈蛋,怎么可能?”,“我就纳闷了,怎么可能出现这种问题呢?”。哈哈,这也许就是大多数攻城狮朋友们最经常发出的感叹吧!
那么,今天我们就来聊聊怎样发现你的错误以及解决一些错误的快速定位方法,而不是等到用户来你这里反馈,因为那时候可能已经错太久了!我们要做的,应该是将错误发现在第一时间,解决在萌芽之中,作出事后总结以避免以后再犯类似错误!因为,错不可怕,可怕的是一直犯同样的错误,那样的话,你和新手有何差别?
说明几点:
1. 本文主要解决的问题是SQL相关的错误;
2. 本文以PHP微视角出发;(妈蛋,谁叫我是从事PHP开发呢?)
3. 本文解决的问题为,1 如何第一时间发现问题;2 如何解决问题;
4. 欢迎质疑、补充;
正题一、如何发现问题?
测试什么的那就不用说了,谁TM敢不过测试就直接上线?如果真是那样,我只能说,你牛逼!测试是一道很重要的防线保障,一般来说,经过测试后的功能,上线之后,Bug不会太多,或者说不会太明显!好吧,我就假设测试一个问题都没有发现,那么, 我们就上线吧!其实,上线之后,我们心里是没有底的,尤其是在某些还没有一套完善的部署系统的公司或项目中,上线前和上线后总会出各种稀奇古怪。到底开发和用户是两个层面的人,鬼知道会发生什么呢?我们悬着的一颗心,竟然要完全依赖于用户的操作,用户的反馈?噢,不,那样,太被动了!
主动发现问题。 一、上线之后,你也不可能再进行测试了,你现在就是一个普通用户,那么,你自己去操作就是必须的,一个大概走下来,基本功能可以确定了,Ok,接下来,真正交给用户! 二、如果没有后手,就真的完全交给用户,那你还是太Low了,因为,必要的监控措施是一定要有的!这里指的监控是程序级别的,也就是所谓的报错。怎样记录报错信息?怎样知道报错了?都说了嘛,我是从事PHP开发的。PHP中有一个记录错误日志的功能,error_reporting,把这个给打开,指定错误日志位置,级别,就可以记录PHP的错误了,但是一定要关闭错误的页面显示,否则,用户看到此类错误,你就完蛋了!既然本文说的是SQL错误,那不应该是这里,没错。但是我要说的是,当SQL报错的时候,PHP也已经发出报错了,通常是一个警告级别的错误,而且这种错误往往是关联性的发生,如下面的语句依赖于上面的查询,而上面已经报错,那么后续也会跟着报错。PHP知道错误了,但是只是大概,那SQL具体哪里错了?只有他自己知道,这种记录就交给他吧!大概原理就是,在查询出错的地方,记录错误日志,错误日志主要记录信息有:错误信息,文件位置(追根溯源一个个文件查上去直到入口),下面是一段示例记录错误信息的PHP代码供参考:
<?php // 记录sql错误日志 private function logError($msg = "") { if (isset($this->_logfile)) { if (!$msg) { if (!mysql_errno()) { return; } $msg = "mysql_errno: " . mysql_errno() . "\nmysql_error: " . mysql_error(); } file_put_contents($this->_logfile, '[' . date('Y-m-d H:i:s') . "] $msg\n", FILE_APPEND); $trace = debug_backtrace(); foreach ($trace as $call) { if(empty($call['file']) && empty($call['line'])) { continue; } file_put_contents($this->_logfile, "{$call['file']} on line {$call['line']}\n", FILE_APPEND); } file_put_contents($this->_logfile, "\n", FILE_APPEND); } }
这样,你就有了一个可供参考的东西了。但是,还有一个关键的问题,就是你怎么知道报错了?那就是报警提示了,你可以使用微信提供的测试号通知自己、你可以使用公司的短信平台给自己发短信、你可以发邮件给自己,然后设置邮件短信提醒!收到信息,赶紧解决去吧,更改的时候,你只需去查看这个文件指示的地方,你就大概知道是什么错了,都知道什么错了,我想,要想解决问题,应该只是个时间问题了,而且是个短时间问题!
正题二、如何解决问题?
其实前面我已经说了,既然已经找到问题根源了,要解决问题只是个短时间问题,但是,我还是将给出一些解决方案作参考,毕竟,大家都这么忙,哪有时间破解你那烂代码!如下是一经常会出现的错误:
1. 报错:[2002] 由于目标计算机积极拒绝,无法连接。解析:数据库连接信息错误,你可能把测试环境连接部署到线上去了,这问题大发了,赶紧恢复吧? 附注:如果没有报错是因为你线上机器可以连接测试库,那问题也大发了;
2. 报错:[1146] Table 'bbbq' doesn't exist。解析:表不存在,赶紧查查,是不是本次新加的表没有被添加到线上吧,或者表名写错了? 附注:一般过了测试的不太可能是表名写错了;
3. 报错:[1054] Unknown column 'column_x' in 'field list'。解析:指定的列不存在,要么库中没有,要么写错了,赶紧查! 附注: 同上;
4. 报错:[1366] Incorrect string value: '\xA9\x96' for column 'x_name' at row 1。解析:字符集问题,如一个gbk的字被字段设置为gb2312接收则会出现此问题,赶紧改回来吧!附注:请尽量使用utf-8编码,代码与入库都方便;
5. 报错:[1364] Field 'pid' doesn't have a default value。解析:指定字段没有默认值,请确认应该获取到的值是否未获取从而为null,为某些不必要字段指定数据库默认值。 附注: 如有索引关系,请一定设置not null 选项,否则索引将可能失效;
6. 报错:[1366] Incorrect integer value: '' for column 'townId' at row 1。解析:给定的值不符合字段类型要求,在入库之前先确认该字段需要什么类型,可做相应强制转换,再入库。 附注:某些版本的mysql可以自行强制转换类型处理此问题,但是结果可能已经超出你的预期;
7. LOAD DATA INFILE ,从文件直接导入数据到数据库
7.1. 报错:[2] File 'D:/wamp/www/a/area.csv' not found (Errcode: 2)。解析:找不到指定的csv文件,确认目录位置是否给定正确,如果正确,确认该文件是否为动态生成而目前尚未生成。 附注:小问题;
7.2. 报错:[13] Can't get stat of '/opt/app/mysql5/var/D:/wamp/www/a/area.csv' (Errcode: 2)或者提示没有权限操作数据库。解析:数据库与web不在同一台服务器,需要指定关键字LOCAL,从而将web与数据库分开。 附注:LOCAL参数是在必要时候使用,因为不指定LOCAL的操作将看起来更安全;
7.3. 报错:[1366] Incorrect integer value: '北京' for column 'provinceId' at row 1。解析:这里使用不指定字段的方式插入数据库,因此,如果csv文件的值顺序与数据库字段顺序不对应的话,将会有很多类似的报错,而这则是致命的,因为全部都错了,即使偶有个别正确入库的,那也是错的你也不会想要的结果,请以正确的顺序写入csv文件或者指定字段; 附注:这里的1366 与前面的1366 意义是不一样的;
7.3. 示例LOAD: LOAD DATA LOCAL INFILE 'D:/wamp/www/a/area.csv' REPLACE INTO TABLE `address` CHARACTER SET utf8 FIELDS TERMINATED BY ',' (`provinceId`, `provinceName`, `cityId`, `cityName`, `areaId`, `areaName`, `townId`, `townName`); 附注:要求每个字段都是有值的,即几个字段就要几个',';
8. [1064] 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 'LIMIT 0, 1000' at line 1。解析:好吧,这是个最通用的错误解释,就是说你的语法写错了。比如 where a_id= order by id desc;这里你原本是想获取一个ID然后去查询,但是后面的ID得到空值,所以整个语法就错了。其实,如果是直接接收参数去查询,这本身可能报错,也是一个注入点,请一定要处理传入的值,一般要求'='号后给''号,整型参数用intval()格式化等等安全意识!
9. 讲解:REPLACE与INSERT 功能其实是差不多的(唯一键是必须的),但是REPLACE会在数据库中存在此记录时先删除再插入,如有自增ID,将会被迅速变大,从而不必手动执行删除操作,而INSERT重复数据时则将报错(可配合UPDATE使用)。适当使用两个功能,解决问题!
...
在真实开发中,其实遇到很多有意思的问题,但一时间也难以想起来,想起来再补充,你也可以下方回复你遇到的问题及解决方案,大家一起参考!