outlook 发表于 2016-11-11 09:29:36

sql反模式分析2

  第八章 多列属性
  目标:存储多值属性  为一个bug设置多个标签
  反模式:创建多个列,为bugs创建tag1,tag2,tag3几个列保存标签。标签必须放于其中一个。
  1.查询数据,比如搜索这三列,可以使用in语句
  2.添加和删除  update bugs set tag1=nullif(tag1,'perfor'),tag2=nullif(tag2,'perfor'),tag3=nullif(tag3,'perfor')这个能把值更新到其中为空的那一列,如果都不为空,则不作更新。            3.确保唯一性。无法确保三列的值不一样。
  4.处理不断增长的值集。三列可能不够用,如果在不断的增加列,性能开销将越来越大,而且sql查询更新将越来越复杂。
  解决方案:创建从属表  
  将具有同样意义的值存在同一列中。
  第九章:元数据分类  案例:为一个客户表增加每年的收入情况,每年的收入情况都存在单独的列中,导致每一年就需要新增一个列存储该年的收入。
  目标:支持可扩展性
  反模式: 克隆表与克隆列
  1.不断产生的新表 按照年份对bug表进行拆分,拆成bugs_2008,bugs_2009等多张表,然后按照需要需要修改对应的sql。
  2.管理数据完整性 如果有数据被误写到其他表中,则可能导致统计的一年的bugs数之类的数据不准确。没有任何办法自动对数据和相关表名做限制。但可以在每张表创建的时候使用check的约束。
  3.同步数据  如果发现某条记录原来是在2009这个表中,但时间弄错了,需要修改为2008的,这样订正数据就比较麻烦。需要好几条sql。
  4.确保唯一性。 如果需要做数据迁移,则需要保证记录的主键id值不会与目标表的主键记录冲突。而且对于那些只支持单表ID唯一的数据库产品,实现这样的功能还需要定义一张额外的表存储产品主键的值。
  5.跨表查询  如果需要查询所有的bugs数,则需要把每个表用union进行查询.
  6.同步元数据.如果值在某个表增加一列,其他表没有增加,则联合查询不用使用*,需要列出所有列名.
  7.管理引用完整性. 其他表就不能引用bugs的外键了,因为有多个bugs表.
  8.标识元数据分裂列 如果有其他表保护bugx_fiexd_2008,bugx_fixed_2009,则以后肯定需要增加bugx_fixed_2010.
  解决方案: 手工分割表的一个合理使用场景是归档数据。把没用的数据迁移到历史表中。
  1.使用水平分区。MYSQL5.1所支持的分区特性,在createtable时执行pritition by hash(year(date)reported)) partitions 4.
  2.使用垂直分区。根据列来对表进行拆分。将一些BLOB或者TEXT字段拆分到其他表存储。
  3.解决元数据分裂列。创建关联表。
  别让数据繁衍元数据。
  物理数据库设计反模式
  第10章:取整错误。
  目标:使用小数取代整数,运算结果必须准确。
  反模式:使用float类型
  无限循环小数无法使用存储表示。
  在SQL中使用FLOAT类型,放大查询结果差异比较大。无法使用比较操作,必须使用近似相等查询,但是阀值需要使用合适。
  解决方案:oracle的FLOAT类型表示的是精确值,而BINARY_FLOAT则是非精确值。
  使用NUMERIC类型。SQL的NUMERIC或者DECIMAL类型来代替FLOAT存储小数。 NUMERIC(9,2) 精度,刻度 这样仍然无法存储无限精度的数据。
  尽可能不要使用浮点数。
  第11章:每日新花样  需要给称呼列加入约束指定这些候选值
  目标:限定列的有效值  希望数据库能够拒绝无效值的输入
  反模式:在列定义上指定可选值。很多数据库设计人员习惯在定义列的时候指定所有可选的有效数据。
  create table bugs(status varchar(20) check(status in('new','in','fixed')).
  mysql也支持用ENUM关键词来约束。但是mysql存储的是序数,而非字符串。
  1.中间的是哪个  无法获得status列中值的枚举列表,如果使用distinct来查询bugs表,但是刚开始没数据,查询的结果为空。如果使用INFORMATION_SHEMA系统视图,则还需要解决解决格式。
  2.添加新口味。添加或者删除一个候选值。没有什么语法支持从ENUM或者check约束中添加或者删除一个值。只能用一个新的集合重新定义这一列。一些数据库只有在表为空表是才能改变某一列的数据。那么就需要先将数据导出,改变之后再导入。
  3.老的口味永不消失。旧的值无法删除。
  4.可一致性地下。check约束,域和UDT在各种数据库支持形式不同意。ENUM是mysql特有的特性。
  解决方案:在数据中指定值,通过创建一张检查表bug_status,定义status列中出现的候选值,然后定义一个外键约束。
  1.查询候选值集合。直接查询检查表。
  2.更新检查表中的数据。插入更新操作很方便。
  3.支持废弃数据。可以通过在bug_status表增加一列来表示是否已经弃用。
  4.良好的可移植性。
  在验证固定集合的候选值时使用元数据。在验证可变集合的候选值时使用数据。
  第12章:幽灵文件  只保存数据库文件,没有保存数据库中保存的文件路径对应的数据库外的文件。
  目标:存储图片或其他多媒体大文件。
  反模式:假设必须使用文件系统,可以使用BLOB字段存储文件,或者只在数据库存储文件路径。
  1.文件不支持DELETE  垃圾回收问题。如果图片在数据库之外,删除某条记录之后无法自动将对应文件删除。
  2.文件不支持事务隔离。数据库事务在提交之前,所有改变对外都不可见。但是数据库之外的文件改变则立刻体现到外界。
  3.文件不支持回滚操作。数据库可以回滚,但是文件系统无法回滚。
  4.文件不支持数据库备份工具。
  5.文件不支持SQL的访问权限设置。
  6.文件不是SQL数据类型。无法验证文件路径是否正确。
  解决方案:在需要时使用BLOB类型。
  MYSQL MEDIUMBLOB:16M oracle:LONGRAW 2GB
  MYSQL有load_file()用来读取一个文件存储到BLOB列
  存储在数据库之外的数据不由数据库管理。
  第13章: 乱用索引
  目标:优化性能
  反模式:无规划的使用索引
  1.无索引
  2.索引过多  不需使用的索引无法获得任何好处,只有开销。
  3.索引也无能为力 常犯的错误是进行一个无法使用索引的查询
  解决方案:所有不重复的值的记录和总计数条数之比越低,索引的效率就越低。
  1.测量  ORACLE:TKProf mysql:慢查询日志
  2.解释  查询执行计划
  3.挑选   索引覆盖
  4.测试 
  5.优化  索引预载入:mysql使用 load index into cache语句。
  6.重建:更新或者删除导致索引修改,需要定期对索引进行维护。mysql:analyze table or optimize table oracle:alter index rebuild
  了解你的数据,了解你的查询请求,然后MENTOR你的索引。
  查询反模式
  第14章:对未知的恐惧。
  目标:辨别悬空值 SQL支持一个特殊的空值,NULL。
  增加记录时使用NULL代替那些还不确定的值。
  一个给定的列如果没有合适的值,可以使用NULL代替。
  当传入参数无效时,一个函数的返回值也可以是NULL。
  在外联结查询中,NULL被用来当做未匹配的列的占位符。
  反模式:将NULL作为普通的值,反之亦然。
  1.在表达式中使用NULL。 如果某个字段为NULL,表达式结果也是NULL。
  2.搜索允许为空的列: select * from bugs where aggin_to=123 或者select * from bugs where not(assin_to=123)都不会返回这列为null的值。
  而且查询null或者非null是不能用where assin_to=NULL或者assin_to<>NULL。使用is null
  3.在查询参数中使用NULL 不能在查询参数assin_to=?传入NULL值
  4.避免上述问题:使用默认值来代替NULL,按时查询计算时仍然需要制定<>默认值
  解决方案:将NULL视为特殊值
  1.在标量表达式中使用NULL 表达式中一个值为NULL,则结果就为NULL。
  2.在布尔表达式中使用NULL。
  3.检索NULL值。SQL-99中额外定义了一个比较断言 IS DISTINCT FROM
  4.声明NOT NULL列。
  5.动态默认值。使用COALESCE()函数返回一个非NULL的参数。
  使用NULL来表示任意类型的悬空值。
  第15章:模棱两可的分组
  目标:获取每组的最大值
  反模式: 引用非分组列
  1.单值规则 一个分组只能返回单一的值 
  2.我想要的查询 如果分组后通过max获得的有两列的值是一样的,那么就无法返回哪条记录的其他列。不能使用max和min两个聚合函数定位到不同的记录。
  解决方案: 无歧义的使用列
  1.只查询功能依赖的列;
  2.使用关联子查询
  3.使用衍生表
  4.使用join 
  5.对额外的列使用聚合函数
  6.连接同组所有值 mysql使用GROUP_CONCAT()函数将这一组中所有的值连在一起。
  遵循单值规则,避免获得模棱两可的查询结果。
  第16章:随机选择  设计一个随机广告展示的查询
  目标:获得样坏死记录
  反模式:随机排序 select * from bugs order by rand() limit 1; 使用rand()简单,但是无法利用索引,因为没有索引会基于随机函数返回的值,导致一次全表排序。
  解决方案:没有具体的顺序。
  1.从1到最大值之间随机选择  select b1.* from bugs as b1 join(select(ceil(rand() *(select max(bug_id) from bugs)) as rand_id) as b2 on(b1.bug_id=b2.bug_id);
  2.选择下一个最大值。 select b1.* from bugs as b1 join(select(ceil(rand() *(select max(bug_id) from bugs)) as rand_id) as b2 where b1.bug_id>=b2.bug_id order by b1.bug_id limit 1;
  3.获得所有键值,随机选择一个。 程序选择一个,查询两次
  4.使用偏移量选择随机行。
  5.专有解决方案。SQL server 使用tablesample函数。 oracle使用sample函数。
  有些查询是无法优化的,换种方式试试看。
  第17章:可怜人的搜索引擎
  目标:全文检索
  反模式:模糊匹配断言 SQL提供了模式匹配断言来比较字符串,最常用的就是like语句。还有REGEXP正则表达式匹配。 不过缺点当然就是性能问题了。
  解决方案:使用正确的工具
  1.数据库扩展 mysql能够对char,varchar,text定义一个全文索引,使用match进行全文查询.oracle使用context支持,然后通过contains()操作符搜索.sqlserver和postgreSQL也有对全文索引的支持.
  2.第三方搜索引擎:Sphinx search lucene
  你不必使用SQL来解决所有的问题.
  第18章:意大利苗条查询
  目标:减少sql查询数量
  反模式:使用一部操作解决复杂问题
  1.副作用 查询多少bug已经修复,多少bug还打开。select p.product_id,count(f.bug_id) as count_fixed,count(o.bug_id) as count_open from bugsproduct p left outer join bugs f on(p.bug_id=f.bug_id and f.status='fixed') left outer join bugx o on(p.bug_id=o.bug_id and o.status='open') where p.product_id=1 group by p.product
  这条sql查询出来count_fixed和count_open都是84,而实际上12个fixed,7个open,刚好84是12*7 这种查询是有问题。
  解决方案:分而治之
  1.分两条sql来查询 。select p.product_id,count(f.bug_id) as count_fixe from bugsproduct p left outer join bugs f on(p.bug_id=f.bug_id and f.status='fixed') where p.product_id=1 group by p.product 。select p.product_id,count(o.bug_id) as count_open from bugsproduct p left outer join bugx o on(p.bug_id=o.bug_id and o.status='open') where p.product_id=1 group by p.product
  2.寻找union标记  根据单个结果集再使用union all合并。
  尽管SQL支持用一行代码解决复杂的问题,但也别做不切实际的事情。
  第19章:隐式的列  联合查询如果两个表有列名一样的列,则会只取其中一个。
  目标:减少输入  可以使用*获取所有列
  反模式:捷径会让你迷失方向
  1.破坏代码重构,比如增加一列之后,原来insert没有指定列名的则现在会报错,少一列的值了。 select也一样,如果删除一列后,应用代码获取列的可能也会出错。
  2.隐藏的开销 获取一些没用的列,会增加网络开销和性能。
  解决方案:明确列出列名
  随便拿,但是拿了就必须吃掉。
  应用程序开发反模式
  第20章 明文密码
  目标:恢复和重置密码  现在一般都是使用邮箱让用户恢复和重置密码
  反模式:使用明文存储密码
  1.存储密码 sql被劫持 
  2.验证密码
  3.在email中发送密码,email有可能被劫持
  解决方案:先哈希,后存储
  mysql扩展支持SHA2()函数返回256位的哈希串
  哈希暴力破解,可以先加密后再进行哈希。
  在SQL中隐藏密码,在程序中生成哈希串之后,在sql中直接使用哈希串。
  重置密码,而非恢复密码。
  如果密码对你可读,那么对于攻击者也是如此。
  第21章:SQL注入
  目标:编写SQL动态查询
  反模式:将未经验证的输入作为代码执行
  解决方案:不相信任何人
  1.过滤输入内容。
  2.参数化动态内容
  3.给动态输入的值加引号
  4.将用户与代码隔离
  5.找个可靠的人来帮你审查代码
  让用户输入内容,但永远别让用户输入代码。
  第22章:伪键洁癖
  目标:整理数据
  反模式:填充角落
  1.不按照顺序分配编号 
  2.为现有行重新编号
  3.制造数据差异 重新主键不是一个好习惯
  解决方案:1.定义行号。2.使用GUID 太长,随机的,需要16字节
  将伪键当做行的唯一性标识,但他们不是行号。
  第23章:非礼勿视
  目标:写更少的代码
  反模式:无米之炊 忽略数据库API的返回值,将程序代码跟SQL混在一起
  1.没有诊断的诊断 在多条sql顺序执行过程中,最好对结果进行诊断,保证错误能够快速定位。
  2.字里行间 花费大量时间调试生成sql字符串的代码
  解决方案:优雅的从错误中恢复
  1.保持节奏 检查数据库API的返回状态和异常。
  2.回溯你的脚步。sql语句记录,输出,调试。
  发现并解决代码中的问题已经很苦难了,就别再盲目的干了。
  第24章:外交豁免权
  目标:最佳实践 使用版本控制工具管理源代码,编写单元测试脚本;编写文档,代码注释。
  反模式:将SQL视为二等公民。
页: [1]
查看完整版本: sql反模式分析2