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视为二等公民。
posted @ 2012-07-22 16:53  MXi4oyu  阅读(204)  评论(0编辑  收藏  举报