由 "select *" 引发的“惨案”

今天凌晨做发布, 要合并多个分数据库的表数据到主数据库中, 有 30+ 分数据库。 前面都比较顺利, 在临近结束时,突然发现一个字段的值插入错误。 有一个表 T,字段分别为 (f1, f2, f3, gmt_create, gmt_modify, name) 。 假设分数据库为 a1, a2, ..., a30 , 主数据库为 A 。 合并的逻辑是: 从 a1, a2, ..., a30 取出对应的字段, 依次插入到 主数据库中。

        aRet = adb.query("select * from T")
        allTuples = []
        for (f1, f2, f3, gmt_create , gmt_modify, name) in aRet:
              allTuples.append((f1, f2, f3, gmt_create, gmt_modify, name))
        Adb.executeMany("insert into T(f1, f2, f3, gmt_create, gmt_modify, name) values(%s, %s, %s, %s, %s, %s)", allTuples)        log.info(allTuples)
        Adb.commit()

NOTE:  这里会将 allTuples 分成 1000 个元组一片进行提交执行。 不过不影响此处的理解。

那么, 这会有什么问题呢? 初看上去似乎没什么, 但是“惨案” 就这样发生了。

有一个集群 ai 的表 T 的字段顺序跟其他集群的略有不同。其表字段顺序是 (f1, f2, f3, name , gmt_create, gmt_modify) 。 这样从 ai 取出的数据 (f1, f2, f3, name, gmt_create, gmt_modify) 将插入到 A 的 (f1, f2, f3, gmt_create, gmt_modify, name) , 也就是说, a1.name 插入到 A.gmt_create,  a1.gmt_create 插入到 A.gmt_modify,  a1.gmt_modify 插入了 A.name  ,   由于 gmt_create ,  gmt_modify, name 均为字符串, 因此没有报错。 验证的时候, 由于另外一个地方因其他原因报了大量错误,掩盖了这个问题。

解决的办法很简单: 将 "select * from T" 改为 "select f1, f2, f3, gmt_create , gmt_modify, name  from T"

教训: 在做“逐字段取出-插入” 的数据库操作时, 切忌使用 “select * ”

发现数据插入错误之后, 马上进行清空和重新执行。 这时, 更糟糕的事情发生了。 由于清空操作要考虑将对 T.A 的新改动(考虑到发布过程中会有外部调用修改T.A的数据)同步到 T.ai 。 而上述已经对 T.A 进行了大量改动,  因此会以 T.A 的数据为准, 对 T.ai 的相应记录进行回写, 结果将 ai 的原数据覆盖了, 且没有预先做表备份。 ai 的数据就这样丢失了!

教训:  合并过程中, 最好不要回写源数据库, 降低复杂性; 如果一定要回写源数据库, 要单独做一个脚本, 取名更明显, 且要做表备份操作。

现在必须马上恢复数据! 当时差点忘了, 由于 db.executeMany 接口没办法获取到直接执行的SQL, 因此昨天早上新添了一行代码使用了 log.info(allTuples) , 记录下了所要插入的源数据。  不过, 由于偷了一点懒, 打印出的 allTuples 的格式相当难以解析, 费了不少劲才将 <primarykey, name> 的关系取出来, 重新做了订正。 昨天早上的那行代码成了今天早上的救命稻草之一。

教训:  对数据库的insert, update, delete 操作一定要加日志。 如果数据库接口不方便直接打印SQL的日志, 就要单独打印出源数据以备后用。 此外, 最好不要偷懒, 因为每一点偷懒都会对后面某个时候造成障碍, 而稍微做的便利一点, 就会对后续产生有益的用处。 这都是活生生的教训。

时间: 2024-10-13 10:00:12

由 "select *" 引发的“惨案”的相关文章

一道题引发的惨案

昨天在一个cocos2d-x的群里,有群友发了一个问题求答案,当时自已也一下子没想到什么好的写法,这时候有个群友写了一个比较少见的代码方法,当时一看到这代码,我有一种如糊灌顶的感受,因为自已平时基本没用这种写法,今日在别的群,我就发了这道题让大家讨论一下,结果没想到,引发了各种争吵和讨论,有写得不好却自以为是的,有写得好却看不起别人的写法,有写得很好却十分低调的,十分精彩,也算是为群里热闹了一下气氛,QQ群也变成了社会市井的一个浓缩了,呵呵. 以下贴上问题和昨天那位群友写的代码,我觉得精彩的地方

CSDN日报20170301——《一次dns缓存引发的惨案》

[程序人生] 一次dns缓存引发的惨案 作者:纯洁的虫子 时间2015年的某个周六凌晨5点,公司官方的QQ群有用户反馈官网打不开了,但有的用户反馈可以打开,客服爬起来自己用电脑试了一下没有问题,就给客户反馈说,可能是自己网络的问题,请过会在试试.早点8点,越来越多的用户反馈官网无法打开,并且有部分用户开发反馈app也打不开了,客服打电话叫起了还在梦乡中的我. -- 点此阅读全文 [Android 开发] Android UI性能优化 检测应用中的UI卡顿 作者:鸿洋 在做app性能优化的时候,大

try{s.send(t.hasContent&amp;&amp;t.data||null)}catch(e){if(n)throw e}}引发的惨案

如题,ajax请求报错:try{s.send(t.hasContent&&t.data||null)}catch(e){if(n)throw e}}引发的惨案 要么是404,要么是400,还有500 搞了好半天,找到jquery.min.js发现这句错误在第一处. 于是:看了我前台获取的日期参数,只有年月日 而后台类注解到了 时:分:秒 因为在前端转换数据的时候,时分秒为空就报错了,于是乎在后台接收把这个字段改成了String类型就行了.只是需要多一遍String 转Date,当然格式要是

记一次项目中yaml文档引发的惨案 (#yaml文档格式#yaml中&#39;-&#39;的作用)

项目已经在收尾阶段了,然后老大让我去把dockerCompose.yaml文件中公用配置给抽取一下,就是说以后改配置啊什么的就可以直接在抽出来的公用变量里面改就行了, 不用一个模块一个模块地去改(我们这个项目是微服务项目,十多个模块),本来是个很没技术含量的活儿,但是呢,引发了一场切(diao)尸吊的话题,来看下原始的配置 文件: 看下官网的语法: 我抽取的: 然后当然就是报错啦, 再然后就是各种检查顺序啊,检查有没有空格的尝试,然后无果,我就和老大汇报说抽不了,如果能抽我切尸吊俩厘米,然后我老

一次dns缓存引发的惨案

时间2015年的某个周六凌晨5点,公司官方的QQ群有用户反馈官网打不开了,但有的用户反馈可以打开,客服爬起来自己用电脑试了一下没有问题,就给客户反馈说,可能是自己网络的问题,请过会在试试.早点8点,越来越多的用户反馈官网无法打开,并且有部分用户开发反馈app也打不开了,客服打电话叫起了还在梦乡中的我. 分析定位 被客服叫起来之后,一脸懵逼,不知道什么情况,给客服回复,知道了,立刻排查,待会有消息及时沟通.用凉水洗了一把脸清醒了一下,立刻根据经验回忆这两天生产投产的情况:上线了XX模块,不影响.修

ogg:Extract 进程遇长事务执行 Forcestop 引发的惨案

http://www.linuxidc.com/Linux/2015-04/115777.htm SQL> select t.addr,t.START_DATE from v$transaction t; ADDR START_DAT ---------------- --------- 0000000136AED710 14-JUL-17 SQL> select t.PREV_SQL_ID from v$session t where taddr='0000000136AED710'; PR

js中this作用域引发的惨案

function MultiTimeOut(count, timer, cb){ var c = count > 1 ? count : 1 var s = false this.run = function(){ if(c > 0 && !s){ cb() c-- setTimeout(this.run, timer) } }   this.stop = function(){ s = true } this.reset = function(){ c = 1 s = fal

AudienceNetwork 引发的惨案

今天临近发布,QA的同事报了很多诡异的bug —— 海外版的.一样的代码,国内版没有问题. 国内版和海外版的主体代码都在一起,而且出问题的部分也没有做拆分,所以运行效果应该完全一样! 但是表现出来的现象就是,海外版不行. 我跟了一下出问题的代码,发现,有的 AsyncTask.doInBackground 就是死活不执行.所以猜想,是不是海外有些代码阻塞了系统的 AsyncTask 的默认线程. 在 Eclipse 里,我还知道是在 DDMS 里查看,但,现在刚转 Android Studio

htons和htonl引发的惨案

什么都不说,先抛出问题: 客户端:connect: Connection refused 调试服务器出现以下问题: port:传入的服务器端口号,设置为8888 srvaddr.sin_port:将本地端口号转化为网络端口号后存放在网络套接口地址数据结构中 这里竟然是0! 此处使用的是htonl 这就是死在htons手下的客户端,将客户端与服务器分割在银河的两端 原因:没有注意网络套接口地址数据结构中sin_port的数据类型,以及误以为htonl可以随意替代htons使用 IPV4通用网络套接