[译] 使用 PostgreSQL 的一些建议

1. 数据库编码

1.1 不要使用 SQL_ASCII 编码

1.1.1 为何

虽然这个名称看起来和 ASCII 有关系,但并不是如此。相反,它只是禁止使用空字节。html

更重要的是,SQL_ASCII 对全部的编码转换的函数而言意味着“不转换”。也就是说,原始字节编码是什么就是什么。除非特别当心,不然 SQL_ASCII 编码的数据库可能最终会存储许多不一样编码的混合数据,可能没法可靠地恢复原始字符。git

1.1.2 何时可使用

若是你的输入数据已是编码的混合的数据,例如 IRC 的日志或非 MIME 兼容的电子邮件,那么 SQL_ASCII 多是最后的方法 - 应该首先考虑使用 bytea 编码,或者能够检测是否为 UTF8 编码,若是非 UTF8 编码,例如 WIN1252 编码的数据能够假定为 UTF8 编码。github

2. 工具

2.1 不要使用 psql -W 或者 psql --password

不要使用 psql -W 或者 psql --password正则表达式

2.1.1 为何

若是使用 --password 或 -W 标志链接服务,psql 会提示你须要输入密码 - 所以即便服务器不须要密码,也会提示你输入密码。算法

这个选项没有必要,它会让人误觉得服务器须要密码。若是你登陆的用户没有设置密码,或者你在提示时输入了错误的密码,你仍然会成功登陆并认为这就是正确的密码 - 但你没法使用这个密码从其余客户端(经过 localhost 链接)或以其余用户身份登陆。sql

2.1.2 何时可使用

不要使用。数据库

2.2 不要使用 RULE

不要使用 RULE,(译者注: CREATE RULE 定义一个适用于特定表或者视图的新规则)若是想要用,请使用触发器替代。安全

2.2.1 为何

RULE 很是强大,但并很差理解。它看起来像是一些条件逻辑,但实际上它会重写查询或向查询中添加其余查询。服务器

这意味着全部 non-trivial 的规则都是不正确的。(译者注:关于 non-trivial 的定义参考引用文章)app

Depesz 对此有更多话要说

2.2.2 何时可使用

不要使用。

2.3 不要使用表继承

不要使用表继承,若是真的要用,可使用外键来替代。

2.3.1 为何

表继承是一个时髦的概念,其中数据库与面向对象的代码紧耦合。事实证实,这些耦合的东西实际上并无产生预期的结果。

2.3.2 何时可使用

几乎不使用……差很少。如今表分区是本地完成的,表继承的常见场景已经被一些特性所取代。

3. SQL 语句

3.1 不要使用 NOT IN

不要使用 NOT IN,或 NOT 和 IN 的任意组合,如 NOT(x IN (select...))。

(若是你认为你想要 NOT IN (select...) 那么你应该使用 NOT EXISTS 替代。)

3.1.1 为何

两个理由:

  1. 若是存在 NULL 值,则 NOT IN 会以意外的方式运行:
select * from foo where col not in (1,null);
  -- always returns 0 rows

select * from foo where col not in (select x from bar);
  -- returns 0 rows if any value of bar.x is null
复制代码

发生这种状况是由于若是 col = 1 则 col IN(1,null) 返回 TRUE,不然返回 NULL(即它永远不会返回 FALSE)。因为 NOT(TRUE) 为 FALSE,但 NOT(NULL) 仍为 NULL,所以 NOT(col IN(1,null)) (与 col NOT IN(1,null)相同)没法返回 TRUE,也就是说 NOT IN (1, NULL) 这种形式永远不会返回数据。

  1. 因为上面的第 1 点,NOT IN (SELECT ...) 不能很好地优化。特别是,规划器(planner 负责生成查询计划)没法将其转换为 anti-join,所以它变为哈希子规划或普通子规划。哈希子规划很快,但规划器只容许该计划用于小结果集;普通的子计划很是慢(事实上是 O(N²)时间复杂度)。这意味着在小规模测试中性能看起来不错,但一旦数据量大,性能就会减慢 5 个或更多个数量级; 咱们不但愿这种状况发生。

3.1.2 何时可使用

NOT IN (list, of, values, ...)只是在列表中有 null 值(参数或其余方式)时,才会有问题。因此在排除没有 null 值时,是能够用的。

3.2 不要使用大写字母命名

不要使用 NamesLikeThis,而是使用 names_like_this 的命名方式。

3.2.1 为何

PostgreSQL 将表,列,函数等名称转换为小写,除非它们使用“双引号”扩起来才不会被转换。

因此 create table Foo() 将会建立一个表名为 foo 的表,执行 create table "Bar"() 才会建立表名为 Bar 的表。

这些查询语句将会正常执行:select * from Foo, select * from foo, select * from "Bar"

这些查询语句会报错 “no such table”:select * from "Foo", select * from Bar, select * from bar

这意味着若是在表名或列名中使用大写字母,则查询时必须使用双引号。这很烦人,可是当你使用其余工具访问数据库时,其中一些名称使用双引号,而另外一些则不使用,这会让人感到困惑。

坚持使用 a-z,0-9 和下划线来表示名称,就没必要再担忧了。

3.2.2 何时可使用

若是在输出中显示好看的名称很重要,那么你可能想使用大写字母。可是你也可使用列别名,也能够在查询中输出好看的名称:select character_name as "Character Name" from foo

3.3 不要使用 BETWEEN(尤为是时间戳)

3.3.1 为何

BETWEEN 使用闭区间比较:范围两端的值会包含在结果中。

这是一个查询的问题

SELECT * FROM blah WHERE timestampcol BETWEEN '2018-06-01' AND '2018-06-08'
复制代码

这将包括时间戳刚好为 2018-06-08 00:00:00.000000 的结果。查询能够工做,可是因为是闭区间,可能在下一次查询会包含这个时刻值(例如 '2018-06-08' AND '2018-06-09' 就会包含那一刻的值)。

用下面的语句替换

SELECT * FROM blah WHERE timestampcol >= '2018-06-01' AND timestampcol < '2018-06-08'
复制代码

3.3.2 何时可使用

BETWEEN 对于整数或日期等离散类型的数据是安全的,须要记住 BETWEEN 是闭区间。但使用 BETWEEN 多是一个坏习惯。

4. 日期/时间 的存储

(译者注:日期/时间 中文文档

4.1 不要使用 timestamp(without time zone)

不要使用 timestamp 类型来存储时间戳,而是使用 timestamptz(也称为带时区的时间戳)来存储。

4.1.1 为何

timestamptz 记录 UTC 的微秒数,你能够插入任什么时候区的值。默认状况下,它将显示当前时区的时间,但你能够转换成其余时区。

由于它存储了时间戳信息,能够用算法来转换成不一样时区的时间戳。

timestamp(也称为没有时区的时间戳)不会执行任何操做,它只会存储你提供的日期和时间。你能够将其视为日历和时钟的图片,而不是时间点,没有时区信息。所以,没有时区的时间戳无法转换时区。

所以,若是你要存储的是时间点,而不是时钟图片,请使用 timestamptz

更多有关 timestamptz 的信息

4.1.2 何时可使用

若是你以抽象方式处理时间戳,或者只是为了 app 的保存和检索,而不对它们进行时间计算,那么 timestamp 多是合适的。

4.2 不要使用 timestamp(without time zone)来存储 UTC 时间

将 UTC 值存储在没有时区的 timestamp ,一般是从其余缺少可用时区支持的数据库继承数据的作法。

改成使用 timestamp with time zonetimestamptz

4.2.1 为何

由于数据库没法知道是不是 UTC 时区。

这让时间计算变得复杂。例如,“给定时区 u.timezone 的最后一个午夜”的计算语句为:

date_trunc('day', now() AT TIME ZONE u.timezone) AT TIME ZONE u.timezone AT TIME ZONE 'UTC'
复制代码

而且“u.timezone 中 x.datecol 日期以前的午夜”的计算语句为:

date_trunc('day', x.datecol AT TIME ZONE 'UTC' AT TIME ZONE u.timezone)
  AT TIME ZONE u.timezone AT TIME ZONE 'UTC'
复制代码

4.2.2 何时可使用

若是与非时区支持数据库的兼容性赛过全部其余考虑因素。

4.3 不要使用 timetz

不要使用 timetz 类型,可使用 timestamptz 代替。

4.3.1 为何

甚至手册也告诉你它只是为了遵照 SQL 标准而实现的。

带有时区的时间类型由 SQL 标准定义,但定义显示的属性让人怀疑它的可用性。在大多数状况下,日期,时间,没有时区的时间戳和带时区的时间戳的组合应该能够提供任何应用程序所需的日期/时间功能。

4.3.2 何时可使用

从不使用。

4.4 不要使用 CURRENT_TIME

不要使用 CURRENT_TIME 函数。使用如下是合适的:

  • CURRENT_TIMESTAMP 或者 now() 若是你想要 timestamp with time zone
  • LOCALTIMESTAMP 若是你想要 timestamp without time zone
  • CURRENT_DATE 若是你想要 date
  • LOCALTIME 若是你想要 time

4.4.1 为何

它返回 timetz 类型的值,关于 timetz 请看上一条解释。

4.4.2 何时可使用

从不使用。

4.5 不要使用 timestamp(0) 或者 timestamptz(0)

不要使用 timestamp() 或者 timestamptz() 进行时间戳的转换(尤为是 0)。

使用 date_trunc('second', blah) 替换。

4.5.1 为何

由于它会将小数部分四舍五入截断它。这可能会致使意外问题;考虑到当你将 now() 存储到这样一个列中时,你可能会在未来存储一个小数秒的值。

4.5.2 何时可使用

从不使用。

5. 文本存储

5.1 不要使用 char(n)

不要使用 char(n),使用 text 可能更适合。

5.1.1 为何

使用 char(n) 类型的字段,若是长度不够会使用空格填充到声明的长度。这可能不是你想要的。

名字 描述
character varying(n), varchar(n) 变长,有长度限制
character(n), char(n) 定长,不足补空白
text 变长,无长度限制

手册上说:

char 类型的数值物理上都用空白填充到指定的长度 n, 而且以这种方式存储和显示。不过,在比较两个 char 类型的值时,尾随的空白是可有可无的,不须要理会。 在空白比较重要的排序规则中,这个行为会致使意想不到的结果, 好比 SELECT 'a '::CHAR(2) collate "C" < 'a\n'::CHAR(2)返回真。 在将 char 值转换成其它字符串类型的时候, 它后面的空白会被删除。请注意, 在 varchar 和 text 数值里, 结尾的空白是有语意的。 而且当使用模式匹配时,如 LIKE,使用正则表达式。

空格填充确实浪费空间,也不会让操做变得更快;事实上,不少状况下咱们还要去掉空格。

提示: 这三种类型之间没有性能差异,除了当使用填充空白类型时的增长存储空间, 和当存储长度约束的列时一些检查存入时长度的额外的 CPU 周期。 虽然在某些其它的数据库系统里,char(n) 有必定的性能优点,但在 PostgreSQL 里没有。 事实上,char(n) 一般是这三个中最慢的, 由于额外存储成本。在大多数状况下,应该使用 text 或 varchar。

5.1.2 何时可使用

当你移植使用了固定宽度字段的很是很是旧的软件时。或者当你阅读上面手册中的片断并认为“是的,这是彻底合理的,而且符合个人要求” 时可使用。

5.2 即便对于固定长度的标识符,也不要使用 char(n)

有时候人们用“个人值恰好是 N 个字符”(例如国家代码,哈希值或来自其余系统的标识符)来回应“为何不要使用 char(n)”。其实,即便在这些场景下使用 char(n) 也不是一个好主意。

5.2.1 为何

对于过短的值,char(n) 会用空格填充它们。所以,带有肯定长度的 char(n) 比较 text 而言没有任何实际好处。

5.2.2 何时可使用

从不使用。

5.3 默认状况下不要使用 varchar(n)

默认状况下不要使用 varchar(n) 类型。考虑使用 varchar(没有长度限制)或 text 替代。

5.3.1 为何

varchar(n) 是一个带长度的文本字段,若是你尝试将长度超过 n 个字符(而不是字节)的字符串插入其中,则会引起错误。

varchar(没有 (n) )或 text 是类似的,没有长度限制。若是在三种字段类型中插入相同的字符串,它们将占用彻底相同的空间,而且性能基本没有差别。

若是你想要一个长度限制的文本字段,那么 varchar(n) 很不错,可是若是你定义姓氏字段为 varchar(20),那么当 Hubert Blaine Wolfeschlegelsteinhausenbergerdorff 注册到你的服务时,将会报错。

有些数据库没有长 text 的类型,或者它们没有像 varchar(n) 那样被良好支持。那这些数据库的用户一般会使用相似 varchar(255) 的表示方法,但他们真正想要的是 text

若是你须要约束字段中的值,好比说约束最大长度 - 或者是最小长度,或者是一组有限制的字符串 - 检查约束能够作到这些。

5.3.2 何时可使用

若是你想要一个文本字段,并且插入太长的字符串须要抛出错误,而且不想使用显式检查约束,那么 varchar(n) 是一个很是好的类型。只是使用时须要多考虑。

6. 其余数据类型

6.1 不要使用 money

money 数据类型实际上不太适合存储货币值。数字或整数可能更好。

6.1.1 为何

大量理由

money 类型存储带有固定小数精度的货币金额。 lc_monetary 用来设置格式化数字。但它的四舍五入的行为可能不是你想要的。

名字 存储容量 描述 范围
money 8 字节 货币金额 -92233720368547758.08 到 +92233720368547758.07

若是你更改了 lc_monetary 设置,则全部 money 列都将包含错误的值。这意味着若是你插入'$ 10.00'而 lc_monetary 设置为 en_US.UTF-8,你检索的值多是'10,00 Lei'或'¥1,000'。

6.1.2 何时可使用

若是你只是用单一货币工做,不处理小数美分而且只作加法和减法运算,那么 money 类型多是正确的。

6.2 不要使用 serial

对于新的应用程序,应使用 identity

6.2.1 为何

serial 类型有一些奇怪的行为,使结构,依赖和权限管理更繁琐。

6.2.2 何时可使用

  • 若是你须要支持 10.0 版以前的 PostgreSQL。
  • 在表继承的某些组合中
  • 更通常地说,若是你以某种方式使用来自多个表的相同序列,尽管在这些状况下,显式声明可能优于 serial 类型。
相关文章
相关标签/搜索