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视为二等公民。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值