GoF 所著的的《设计模式》,在软件领域引入了“设计模式”(design pattern)
的概念。html
然后,Andrew Koenig 在 1995 年造了 反模式(anti-pattern)
(又称反面模式)这个词,灵感来自于 GoF 所著的的《设计模式》。前端
反模式指的是在实践中常常出现但又低效或是有待优化的设计模式,是用来解决问题的带有共同性的不良方法。它们已经通过研究并分类,以防止往后重蹈覆辙,并能在研发还没有投产的系统时辨认出来。mysql
因此,反模式是特殊的设计模式,而这种设计模式是欠妥的,起到了反效果。程序员
但有的时候,出于权衡考量,也会使用反模式。正则表达式
例如数据库的结构中使用的
反规范化
设计。sql
下面的每一章,都会列举一种特定场景下的反模式,而后再给出避免使用反模式的建议。数据库
有个别章节,我略去了反模式,直接写解决方案了。编程
假设有 Product 和 Account 两个实体。设计模式
假设:Product 只有一个 Account(即 Account 也只有一个 Product)。数组
方案:只用一张表,用两个字段(Product + Account)关联便可。
如无必要,就别用多个表,这会增长复杂度(除非考虑将来的拓展性等其余状况)。
假设:Product 能够有多个 Account。
方案1:两张表,一个 Product 表,一个 ProductAccount 表,此表存 ProductId + AccountName。
ProductAccount 表称之为
从属表
。
方案2:只用一张表,即 Product 表,而后此表有个 Account 字段,存以逗号分隔的 AccountName。
此为反模式,不推荐使用。
方案3:还有一种拓展性更好的、也是本人工做中更经常使用的作法,直接用下面 ”三、多对多关系“ 的方案 。
假设:Product 能够有多个 Account,Account 也能够有多个 Product。
方案:用三张表,一个 Product 表,一个 Account 表,一个 ProductAccount 表,此表存 ProductId + AccountId。
ProductAccount 表称之为
交叉表
。
创建一张表,存放(帖子的)评论(可嵌套回复评论)。
添加 parent_id
列,指向同一张表的id。
这样的设计叫作邻接表
。这多是程序员们用来存储分层结构数据中最普通的方案了。
缺点:
[拓展]
某些品牌的数据库管理系统提供扩展的 SQL 语句,来支持在邻接表中存储分层数据结构。
递归查询
的表达式规范,使用 WITH
关键字加上公共表表达式。这里的递归查询暂时不深究,待写。
创建一个 path
字段,存路径,如1/4/6/7/
缺点:
能够用 PG 的 text 类型,最高支持存储 1G 的字符串,应该是够了。
创建 nsleft
和 nsright
字段,存储子孙节点的相关信息,而不是节点的直接祖先.
每一个节点经过以下的方式肯定 nsleft 和nsright 的值:nsleft 的数值小于该节点全部后代的ID,同时 nsright 的值大于该节点全部后代的ID。这些数字和 comment_id 的值并无任何关联。
肯定这三个值(nsleft,comment_id,nsrigh)的简单方法是对树进行一次深度优先遍历,在逐层深刻的过程当中依次递增地分配 nsleft 的值,并在返回时依次递增地分配 nsright 的值。
最后结果形如:
缺点:
若是简单快速地查询是整个程序中最重要的部分,嵌套集是最佳选择——比操做单独的节点要方便快捷不少。然而,嵌套集的插入和移动节点是比较复杂的,由于须要从新分配左右值,若是你的应用程序须要频繁的插入、删除节点,那么嵌套集可能并不适合。
闭包表是解决分级存储的一个简单而优雅的解决方案,它记录了树中全部节点间的关系,而不只仅只有那些直接的父子关系。
在设计评论系统时,咱们额外建立了一张叫作 TreePaths
的表,它包含两列:ancestor
和 descendant
,每一列都是一个指向评论表的id的外键。
TreePaths 表结构以下:
设计 | 表 | 查询子 | 查询树 | 插入 | 删除 | 引用完整性 |
---|---|---|---|---|---|---|
邻接表 | 1 | 简单 | 困难 | 简单 | 简单 | 是 |
递归查询 | 1 | 简单 | 简单 | 简单 | 简单 | 是 |
枚举路径 | 1 | 简单 | 简单 | 简单 | 简单 | 否 |
嵌套集 | 1 | 困难 | 简单 | 困难 | 困难 | 否 |
闭包表 | 2 | 简单 | 简单 | 简单 | 简单 | 是 |
邻接表是最方便的设计,而且不少软件开发者都了解它。
若是你使用的数据库支持W ITH 或者 CONNECT BY PRIOR 的递归查询,那能使得邻接表的查询更为高效。
闭包表是最通用的设计,而且本章所描述的设计中只有它能容许一个节点属于多棵树。它要求一张额外的表来存储关系,使用空间换时间的方案减小操做过程当中由冗余的计算所形成的消耗。
我以前作过的评论功能,需求都会尽可能简化,例如弄成扁平化,只能回复评论一次,即不能评论评论的评论。若是下次鄙人真的要实现这个复杂的评论功能了,关于闭包表的具体设计及操做实现,准备回头再看原书。
在这样的表中,须要引入一个对于表的域模型无心义的新列来存储一个伪值。这一列被用做这张表的主键,从而经过它来肯定表中的一条记录。这种类型的主键列咱们一般称其为伪主键
或者代理键。
能够把 伪主键 理解成 伪键 或者 主键。
按照关系型数据库的定义,表里是不能够出现重复行的,可是实际中确实会出现,怎么办,引入伪键就不会重复了。
伪主键直到 SQL:2003 才成为一个标准,于是每一个数据库都使用本身特有的 SQL 扩展来实现伪主键,甚至不一样数据库中对于伪主键都有不一样的名称(不一样的表述),以下表:
名称 | 数据库 |
---|---|
AUTO_INCREMENT | MySQL |
GENERATOR | Firebird, InterBase |
IDENTITY DB2 | Derby, Microsoft SQL Server, Sybase |
ROWID | SQLite |
SEQUENCE DB2 | Firebird, Informix, Ingres, Oracle, PostgreSQL |
SERIAL | MySQL, PostgreSQL |
虽然各家数据库产品的伪主键叫法不一样,可是给伪主键指派的列名,确是出奇的一致,那就是 id
。
有时你被迫使用不支持外键约束的数据库产品(好比 MySQL 的 MyISAM 存储引擎,或者比 SQLite 3.6.19 早的版本)。
若是是这种状况,那你不得不使用别的方法来弥补。
外键的好处:
总结来看就是:避免编写没必要要的代码,节省了大量开发、调试以及维护时间。
软件行业中每千行代码的平均缺陷数约为 15~50 个。在其余条件相同的状况下,越少的代码,意味着越少的缺陷。
外键的缺点:
但这是值得的。
表支持可变(可拓展)属性(列)。
例如:你有一个 Prodcut 表,记录了两种类型的产品:
对于某些程序员来讲,当他们须要支持可变属性时,第一反应即是建立另外一张表,将属性当成行来存储。
这样的设计称为实体—属性—值
,简称EAV
。有时也称之为:开放架构、无模式或者名—值对。
例如:
ProdcutAttr 表数据形如:
- (1,1, "product_type", "电影")
- (2,1, "product_name", "阿甘正传")
- (3,1, "total_duration", 120)
- (4,2, "product_type", "图书")
- (5,2, "product_name", "简爱")
- (6,2, "total_page", 300)
最简单的设计是将全部相关的类型都存在一张表中,为全部类型的全部属性都保留一列。同时,使用一个属性来定义每一行表示的子类型。在这个例子中,这个属性称做issue_type。
对于全部的子类型来讲,既有一些公共属性,但同时又有一些子类型特有属性。这些子类型特有属性列必须支持空值,由于根据子类型的不一样,有些属性并不须要填写,从而对于一条记录来讲,那些非空的项会变得比较零散。
例如:
Prodcut 表数据形如:
- (1,"电影", "阿甘正传", 120, NULL)
- (2,"图书", "简爱", NULL, 300)
缺点:
适用场景:
为每一个子类型建立一张独立的表。每一个表包含那些属于基类的共有属性,同时也包含子类型特殊化的属性。
例如:
缺点:
能够建立一个视图联合这些表,仅选择公共的列。
此种方法模拟了继承,把表当成面向对象里的类。建立一张基类表
,包含全部子类型的公共属性。对于每一个子类型,建立一个独立的表,经过外键和基类表相连。
这里须要用到数据库产品自带的表继承功能。
例如:
使用一个BLOB 列来存储数据,用 XML 或者 JSON 格式——同时包含了属性的名字和值。Martin Fowler 称这个模式为:序列化大对象块(Serialized BLOB)
。
优势:优异的扩展性
缺点:就是在这样的一个结构中,SQL 基本上没有办法获取某个指定的属性。你不能在一行blob 字段中简单地选择一个独立的属性,并对其进行限制、聚合运算、排序等其余操做。你必须获取整个blob 字段结构并经过程序去解码而且解释这些属性。
但如今的数据库,例如 PG,能够直接支持使用 JSON(B) or XML 的数据类型。因此不会存在必须整个获取再解析的麻烦了。
怎么声明一个指向多张表的外键?
例如,Comments 表的外键(issue_id)要引用 Bugs 表 or FeatureRequests 表。形如(这种写法是无效的):
FOREIGN KEY (issue id) REFERENCES Bugs (issue_id) OR FeatureRequests (issue_id)
有一个解决方案已经流行到足以正式命名了,那就是:多态关联
。有时候也叫作杂乱关联。
例如:
除了 Comments 表 issue_id 这个外键以外,你必须再添加一列:issue_type
,这个额外的列记录了当前行所引用的表名,取值范围是 "Bugs" / "FeatureRequests"。
缺点:没有任何保障数据完整性的手段来确保 Comments.issue_id 中的值在其父表中存在。
当你使用一个面向对象的框架(诸如Hibernate)时,多态关联彷佛是不可避免的。这种类型的框架经过良好的逻辑封装来减小使用多态关联的风险(即依赖上层程序代码而不是数据库的元数据)。若是你选择了一个成熟、有信誉的框架,那能够相信框架的做者已经完整地实现了相关的逻辑代码,不会形成错误。
把 Comments 表向下拆分,分出两个多的交叉表,即 BugsComments
和 FeatureRequestsComments
基于 Bugs 表和 FeatureRequests 表,建立共用的超级表:Issues
。
例如: 有一个 Bug 表,每一个 Bug 自身可能会有多个 tag。
CREATE TABLE Bug bug_id SERIAL PRIMARY KEY description VARCHAR (1000) tagl VARCHAR (20) tag2 VARCHAR (20) tag3 VARCHAR (20)
每次要修改 Bug 自身的最大 tag 数,会动表结构,可拓展性不好。
在原有 Bug 表的基础上,再建立一个 BugTag 表。包含下面几列:
用形如 Crevenue200二、Crevenue200三、Crevenue2004 的多列,来记录销售额。
这里的问题在于部分数据存在于列名中,即混淆了元数据和数据。
还有一种常见的反模式是,将数据(年份)追加在基本表名以后。
若是是由于同一张表数据量太多致使这种反模式,建议:
你仅须要定义一些规则来拆分一张逻辑表,数据库会为你管理余下的全部事情。物理上来讲,表的确是被拆分了,但你依旧能够像查询单一表那样执行SQL 查询语句。
分区在 SQL 标准中并无定义,所以每一个不一样的数据库实现这一功能的方式都是非标准的。
鉴于水平分区是根据行来对表进行拆分的,垂直分区就是根据列来对表进行拆分
好比说,会在Products 表中为每一个单独的产品存储一份安装文件。这种文件一般都很大,但BLOB 类型的列能够存储庞大的二进制数据。若是你有使用通配符“*”进行查询的习惯,那么将如此大的文件存储在Products 表中,并且又不常用,很容易就会在查询时遗漏这一点,从而形成没必要要的性能问题。
正确的作法是将BLOB 列存在另外一张表中,和Products 表分离但又与其相关联。
把列转为行。
关于计算机二进制浮点数表示法致使的精度丢失和取整错误,能够看我这一篇:《关于 JavaScript 的 精度丢失 与 近似舍入》,原理是同样的。
解决方案:使用 SQL 中的 NUMERIC
或 DECIMAL
类型来代替 FLOAT 及与其相似的数据类型进行固定精度的小数存储。
哪怕不是存小数而是存整数,也不要用 FLOAT!一样会存在错误隐患。
需求:限定列的有效值。
一、CHECK 约束
缺点:
二、域
缺点:属于数据库高级操做,杀鸡焉用牛刀。不赘述了。
三、用户自定义类型(UDT)
缺点:属于数据库高级操做,杀鸡焉用牛刀。不赘述了。
一、使用枚举类型 ENUM
优势:
二、建立一个单独的表,存列的有效值,其余表使用外键引用
优势:
原始图片文件能够以二进制格式存储在 BLOB
类型中,就像以前咱们存储超长字段那样。
然而,不少人选择将图片存储在文件系统中,而后在数据库里用 VARCHAR 类型来记录对应的路径。这实际上是一种反模式。
具体要不要用这种反模式,见仁见智,要按照具体使用场景来判断。
如今广泛仍是流行这种反模式,例如我司,由于静态资源都是上传到 OSS 托管,有 CDN 加成。
略
假设:咱们有 test 表:
id | left | right |
---|---|---|
1 | 111 | 222 |
2 | 333 | 333 |
3 | 444 | NULL |
4 | NULL | 555 |
5 | NULL | NULL |
正确结果是 id 为 一、三、4 的行。
错误方法:直接使用 where "left" != "right"
,但 !=
对 NULL 无效。
正以下面这个例子:
select 1 != 1; #f select 1 != 2; #t select 1 != NULL; #null(不是咱们想要的结果,应该返回 t) select NULL != NULL; #null(不是咱们想要的结果,应该返回 f)
后两种状况结果为 NULL,是由于 sql 是三值逻辑而不是二值逻辑,具体能够看我以前的一篇:《SQL基础教程》+《SQL进阶教程》学习笔记,里面有详细介绍。
将 NULL 视为特殊值,额外用 IS ( NOT ) NULL
判断:where "left" != "right" or ( "left" is null and "right" is not null ) or ( "left" is not null and "right" is null )
这种写法很累赘。
直接用 IS DISTINCT FROM
,即:where "left" IS DISTINCT FROM "right"
,不须要额外对 NULL 判断。
IS ( NOT ) DISTINCT FROM
的支持状况:
每一个数据库对 IS ( NOT ) DISTINCT FROM 的支持是不一样的。PostgreSQL、IBM DB2 和 Firebird 直接支持它,Oracle 和 Microsoft SQL Server 暂时还不支持。MySQL 提供了一个专有的表达式 <=>,它的工做逻辑和 IS NOT DISTINCT FROM 一致。
例如,有 test 表:
id | type | name | join_time |
---|---|---|---|
1 | 老师 | 赵老师 | 2020-01-01 |
2 | 老师 | 钱老师 | 2020-01-02 |
3 | 同窗 | 张三 | 2020-01-03 |
4 | 同窗 | 李四 | 2020-01-04 |
5 | 同窗 | 王五 | 2020-01-05 |
需求:咱们须要在 老师 or 同窗 分别里找出 join_time 最先的一条记录。
执行 SELECT "type", MIN("join_time"), "name" FROM "test" GROUP BY "type"
name 列就是有歧义的列,可能包含不可预测的和不可靠的数据:
解决方案:无歧义地使用列。
最直接的解决方案就是将有歧义的列排除出查询。
执行 SELECT "type" FROM "test" GROUP BY "type"
但这知足不了咱们的需求,pass。
执行 SELECT "type", MIN("join_time"), MIN("name") FROM "test" GROUP BY "type"
若是不能保证 MIN("join_time")
和 MIN("name")
是指向同一行,那这个写法就是错的。有风险,pass。
SELECT * FROM test as t1 WHERE NOT EXISTS ( SELECT * FROM test as t2 WHERE t1."type" = t2."type" and t1.join_time > t2.join_time )
缺点:性能很差。
[拓展] 用关联子查询写出来的思路:
涉及 SQL 基础的全程量化和存在量化的知识点,详细可参考个人旧文:《SQL基础教程》+《SQL进阶教程》学习笔记
若是需求变成:咱们须要在 老师 or 同窗 分别里找出 join_time 最晚的一条记录,那只须要把 t1.join_time > t2.join_time
变成 t1.join_time < t2.join_time
便可:
SELECT * FROM test as t1 WHERE NOT EXISTS ( SELECT * FROM test as t2 WHERE t1."type" = t2."type" and t1.join_time > t2.join_time )
SELECT * FROM test as t1 INNER JOIN ( SELECT "type", MIN("join_time") as "join_time" FROM test GROUP BY "type" ) as t2 ON t1.join_time = t2.join_time
缺点:性能很差。
若是需求变成:咱们须要在 老师 or 同窗 分别里找出 join_time 最晚的一条记录,那只须要把 MIN("join_time")
变成 MAX("join_time")
便可:
SELECT * FROM test as t1 INNER JOIN ( SELECT "type", MAX("join_time") as "join_time" FROM test GROUP BY "type" ) as t2 ON t1.join_time = t2.join_time
SELECT * FROM test as t1 LEFT JOIN test as t2 ON t1."type" = t2."type" AND ( t1.join_time > t2.join_time ) WHERE t2."id" IS NULL
解释:t1.join_time > t2.join_time
搭配 WHERE t2."id" IS NULL
是利用 LEFT JOIN 的特性,即若是找到匹配行则能够生成多行,但若找不到匹配行,则另外一边置 NUll。
缺点:性能稍好,可是较难维护。
若是需求变成:咱们须要在 老师 or 同窗 分别里找出 join_time 最晚的一条记录,那只须要把 t1.join_time > t2.join_time
变成 t1.join_time < t2.join_time
便可:
SELECT * FROM test as t1 LEFT JOIN test as t2 ON t1."type" = t2."type" AND ( t1.join_time < t2.join_time ) WHERE t2."id" IS NULL
SELECT * FROM ( SELECT *, RANK() OVER ( PARTITION BY "type" ORDER BY "join_time" ASC ) AS "rank" FROM test ) as t1 WHERE "t1"."rank" = 1
关于更多窗口函数的介绍,可看个人旧文:《SQL基础教程》+《SQL进阶教程》学习笔记
若是需求变成:咱们须要在 老师 or 同窗 分别里找出 join_time 最晚的一条记录,那只须要把 ASC
变成 DESC
便可:
SELECT * FROM ( SELECT *, RANK() OVER ( PARTITION BY "type" ORDER BY "join_time" DESC ) AS "rank" FROM test ) as t1 WHERE "t1"."rank" = 1
相比于将整个数据集读入程序中再取出样例数据集,直接经过数据库查询拿出这些样例数据集会更好。
本章的目标就是要写出一个仅返回随机数据样本的高效 SQL 查询。
SELECT * FROM test ORDER BY random() limit 1
缺点:
一种避免对全部数据进行排序的方法,就是在 1 到最大的主键值之间随机选择一个。
但要考虑 1 到最大值之间有缝隙的状况。
利用 JOIN
:
SELECT t1.* FROM test AS t1 JOIN ( SELECT CEIL( random() * ( SELECT MAX ( "id" ) FROM test ) ) AS "id" ) AS t2 ON t1."id" >= t2."id" ORDER BY t1."id" LIMIT 1
计算总的数据行数,随机选择0 到总行数之间的一个值,而后用这个值做为位移来获取随机行。
利用 OFFSET
:
SELECT * FROM test LIMIT 1 OFFSET ( SELECT CEIL( random() * ( SELECT COUNT ( * ) FROM test ) ) - 1 )
每种数据库均可能针对这个需求提供独有的解决方案:
-- Microsoft SQL Server 2005 增长了一个 TABLE-SAMPLE 子句。
-- Oracle 使用了一个相似的 SAMPLE 子句,好比返回表中1%的记录。
-- Postgres 也有相似的叫 TABLESAMPLE
。
可是这种采样的方法返回结果的行数很不稳定,感受仍是不推荐了。
全文搜索。
使用 LIKE 或者正则表达式进行模式匹配搜索。
缺点:使用模式匹配操做符的最大缺点就在于性能问题。它们没法从传统的索引上受益,所以必须进行全表遍历。
解决方案:使用正确的工具。
每一个大品牌的数据库都有对全文搜索这个需求的解决方案。
例如,PostgreSQL 8.3 提供了一个复杂的可大量配置的方式,来将文本转化为可搜索的词聚集合,而且让这些文档可以进行模式匹配搜索。即,为了最大地提高性能,你须要将内容存两份:一份为原始文本格式,另外一份为特殊的 TSVECTOR
类型的可搜索格式。
空间换时间。
① 步骤:
建表时建立 TSVECTOR
数据类型的列。
② 步骤:
你须要确保 TSVECTOR 列的内容和你所想要搜索的列的内容同步。PostgreSQL 提供了一个内置的触发器来简化这一操做。
触发器写法略,可看原书。
③ 步骤:
你也应该同时在 TSVECTOR 列上建立一个反向索引(GIN)。
写法略,可看原书。
④ 步骤:
在作完这一切以后,就能够在全文索引的帮助下使用PostgreSQL 的文本搜索操做符@@
来高效地执行搜索查询。
写法略,可看原书。
太复杂,略。
你没必要使用 SQL 来解决全部问题。
两个产品:Sphinx Search
和 Apache Lucene
。
使用略,可看原书。
一条精心设计的复杂 SQL 查询,相比于那些直接简单的查询来讲,不得不使用不少的JOIN、关联子查询和其余让 SQL 引擎难以优化和快速执行的操做符。而程序员直觉地认为越少的SQL 执行次数性能越好。
目标:减小 SQL 查询数量。
解决方案:
好处:
我所遇到的程序员使用SQL通配符时问得最多的问题是:“有没有选择除了几个我不想要的列以外全部列的方法?
答案是“没有”。
其实我仍是但愿数据库厂商能加上,如今网上有不少 hack 的方法,需求毕竟是在的。诶。
解决方案:明确列出列名,而不是使用通配符或者隐式列的列表。
能够参考我以前的文章:《数据库里帐号的密码,须要怎样安全的存放?—— 密码哈希(Password Hash)》
防止 SQL 注入。
好比,在PHP 的 PDO 扩展中,可使用一个 quote()函数来定义一个包含引号的字符串或者还原一个字符串中的引号字符。
解决方案:不信任任何人。
Node.js 的 joi 库。
用上一条的过滤库也能够实现。
你应该使用查询参数将其和 SQL 表达式分离。
没有哪一种 SQL 注入的攻击可以改变一个参数化了的查询的语法结构。
缺点:
① 会影响优化器的效果,最终影响性能。
好比说,假设在 Accounts 表中有一个 is_active 列。这一列中99%的记录都是真实值。对 is_active = false 的查询会得益于这一列上的索引,但对于 is_active = true 的查询却会在读取索引的过程当中浪费不少时间。然而,若是你用了一个参数 is_active = ? 来构造这个表达式,优化器不知道在预处理这条语句的时候你最终会传入哪一个值,所以颇有可能就选择了错误的优化方案。
要规避这样的问题,直接将变量内容插入到SQL 语句中会是更好的方法,不要去理会查询参数。一旦你决定这么作了,就必定要当心地引用字符串。
能够结合下面的 ”(3)将用户与代码隔离“ 一块儿使用。
② 这还不是一个通用的解决方案,由于查询参数总被视为是一个字面值。
例如:
解决方案:使用了一些 PHP 内置的数组函数来生成一个占位符数组。
解决方案:能够用存储过程;或者经过事先在应用逻辑代码里,先用字符串拼接的方式生成好 sql 代码。
③ 很差调试
这意味着,若是你获取到一个预先准备好的SQL 查询语句,它里面是不会包含任何实际的参数值的。当你调试或者记录查询时,很方便就能看到带有参数值的SQL 语句,但这些值永远不会以可读的SQL 形式整合到查询中去。
解决方案:调试动态化SQL 语句的最好方法,就是将准备阶段的带有占位符的查询语句和执行阶段传入的参数都记录下来。(本身动手,丰衣足食。)
你可能看过数据访问框架的拥护者声称他们的库可以抵御全部SQL 注入的攻击。对于全部容许你使用字符串方式传入SQL 语句的框架来讲,这都是扯淡。
没有任何框架能强制你写出安全的 SQL 代码。一个框架可能会提供一系列简单的函数来帮助你,但很容易就能绕开这些函数,而后使用一般的修改字符串的办法来编写不安全的SQL语句。
就是你得保证本身写的是符合框架规范的写法,否则人为因素仍是会致使出错。
略
将请求的参数做为索引值去查找预先定义好的值,而后用这些预先定义好的值来组织 SQL 查询语句。
例如把请求的参数通过 if else ,来分配进预先定义好的 SQL 查询语句。
找到瑕疵的最好方法就是再找一双眼睛一块儿来盯着看。
有条件能够结对编程。
不能忍受主键中间出现不连续的缺位。
重用主键并非一个好主意,由于断档每每是因为一些合理的删除或者回滚数据所形成的。
它们不必定非得是连续值才能用来标记行。
将伪键当作行的惟一性标识,但它们不是行号。别把主键值和行号混为一谈。
问:怎么抵挡一个但愿清理数据库中伪键断档的老板的请求?
答:这是一个沟通方面的问题,而不是技术问题。
GUID
(Globally Unique Identifier 全局惟一标识符)是一个128 位的伪随机数(一般使用32 个十六进制字符表示)。
GUID 也称
UUID
(Universally unique identifier 通用惟一标识符)。
GUID 相比传统的伪键生成方法来讲,至少有以下两个优点:
“我不会让错误处理弄乱了个人代码结构的。”
致使问题:
一些计算机科学家推测在一个稳固的程序中,至少有50%的代码是用来进行错误处理的。
全部喜欢跳舞的人都知道,跳错舞步是不可避免的。优雅的秘诀就是弄明白怎么挽回。给本身一个了解错误产生缘由的机会,而后就能够快速响应,在任何人注意到你出丑以前,神不知鬼不觉地回到应有的节奏上。
技术债务
(technical debt),是程序设计及软件工程中的一个比喻。指开发人员为了加速软件开发,在应该采用最佳方案时进行了妥协,改用了短时间内能加速软件开发的方案,从而在将来给本身带来的额外开发负担。这种技术上的选择,就像一笔债务同样,虽然眼前看起来能够获得好处,但必须在将来偿还。软件工程师必须付出额外的时间和精力持续修复以前的妥协所形成的问题及反作用,或是进行重构,把架构改善为最佳实现方式。
还有些工具可以经过SQL 脚本或者运行中的数据库直接经过反向工程获得 ER 图。
[拓展] 版本管理 之 管理数据库:
版本管理工具管理了代码,但并无管理数据库。Ruby on Rails 提供了一种技术叫作“迁移
”,用来将版本控制应用到数据库实例的升级管理上。
大多数其余的网站开发框架,包括PHP 的Doctrine、Python 的Django 以及微软的 ASP.NET,都支持相似于Rails 的“迁移”这样的特性。
我目前 Node.js 用的 sequelize 就包含了这种数据库的迁移脚本。
缺点:但它们还不是完美的,只能处理一些简单类型的结构变动。并且从根本上说,它们在原有版本控制服务以外又创建了一个版本系统。
略
所谓专家,就是在一个很小的领域里把全部错误都犯过了的人。 —— 尼尔斯·玻尔
规范仅仅在它有帮助时才是好的。
Mitch Ratcliffe 说:“计算机是人类历史中最容易让你犯更多错误的发明……除了手枪和龙舌兰以外。”