由 "select *" 引发的“惨案”
2014-12-31 12:03
267 查看
今天凌晨做发布, 要合并多个分数据库的表数据到主数据库中, 有 30+ 分数据库。 前面都比较顺利, 在临近结束时,突然发现一个字段的值插入错误。 有一个表 T,字段分别为 (f1, f2, f3, gmt_create, gmt_modify, name) 。 假设分数据库为 a1, a2, ..., a30 , 主数据库为 A 。 合并的逻辑是: 从 a1, a2, ..., a30 取出对应的字段, 依次插入到 主数据库中。
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) , 记录下了所要插入的源数据。 万一出问题, 避免从 DB 中取执行SQL的麻烦。 不过, 由于偷了一点懒, 打印出的 allTuples 的格式相当难以解析, 费了不少劲才将 <primarykey, name> 的关系取出来, 重新做了订正。 昨天早上的那行代码成了今天早上的救命稻草之一。
教训: 对数据库的insert, update, delete 操作一定要加日志。 如果数据库接口不方便直接打印SQL的日志, 就要单独打印出源数据以备后用。 此外, 最好不要偷懒, 因为每一点偷懒都会对后面某个时候造成障碍, 而稍微做的便利一点, 就会对后续产生有益的用处。 这都是活生生的教训。
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) , 记录下了所要插入的源数据。 万一出问题, 避免从 DB 中取执行SQL的麻烦。 不过, 由于偷了一点懒, 打印出的 allTuples 的格式相当难以解析, 费了不少劲才将 <primarykey, name> 的关系取出来, 重新做了订正。 昨天早上的那行代码成了今天早上的救命稻草之一。
教训: 对数据库的insert, update, delete 操作一定要加日志。 如果数据库接口不方便直接打印SQL的日志, 就要单独打印出源数据以备后用。 此外, 最好不要偷懒, 因为每一点偷懒都会对后面某个时候造成障碍, 而稍微做的便利一点, 就会对后续产生有益的用处。 这都是活生生的教训。
相关文章推荐
- 【Webview相关问题】"控制字符"引发的惨案及Base64
- mybatis中 insert into select 批量生成uuid时 用replace去掉"-"
- mybatis中 insert into select 批量生成uuid时 用replace去掉"-"只生成一个uuid
- mybatis 缓存getSqlSession().selectList(getMapperNamesapce() + ".getList", e);
- "build gradle project info" Android Studio 导入Gradle工程时引发该问题的解决办法
- 网上零售年会召开 "微商未来"引发热议
- "Only the original thread that created a view hierarchy can touch its views"引发的思考_Handler的使用
- 加了 runat="server"属性的select控件的value和text(实际值和显示文本)
- Mysql - 02 mysql数据库"查询"select相关命令
- 使用LinqDataSource出现“找不到与ViewState中存储的原始值中的给定键相匹配的行,请确保"keys"字典包含与上一个Select操作返回的行对应的唯一键值”错误的解决办法
- Hibernate的fetch="join"和fetch="select"
- 一条Select语句引发的反思
- 由<a href = "#" > 引发的思考
- end tag for "select" which is not finished
- char str1[11] = "12345\06789"; 引发的深刻教训---原来'\067'与'7'是相等的
- 由"@id/myid"引发的一场血案
- (干货)自定义 type="range" type="radio" select的样式
- Db2中"SELECT * FROM TABLE"时返回记录的顺序
- htons和htonl引发的惨案
- 一个 Google 汉堡引发的惨案