对于主键,作为开发者在学生时代就要学习,进入职场,开始充斥各种类型的主键,有.net系的 GUID 做主键,也有 java 系的用 uuid ,当然这俩基本就是一回事,然后还有了雪花id,自增id做主键,以及现在常用的自增id做主键,雪花id做逻辑主键,这都是常用的主键实践方案,我们在这里不讨论孰优孰劣,因为就算分出个高下,也不能改变的存量系统的现状。本篇我们来讨论一下自增主键,尤其是在PostgreSQL下的自增主键的问题。这个问题首先就要从不同数据库的差异讲起。

1. Oracle

多年以前,在一家公司使用的数据库是 Oracle 数据库,发现与.net程序员认知的数据库( sqlserver )也是有差异的:设置自增主键,Oracle需要创建序列,如何创建?有兴趣的可以去百度,这里就不浪费篇幅了,毕竟以后还会再使用oracle数据库机会近乎等于0。大概就是需要创建一个序列,这个序列有一个初始值,有个步长,每select一次,当前值就往前累加一个步长,oracle中,数据表每次insert 都会默认从序列中select序列一个值。然后实现了自增。

2.sqlserver和mysql

没用过 oracle 的童鞋应该马上就发现了在熟知的数据库操作的不同:我们在 sqlservermysql 是没这样的操作,前者可以直接指定主键为标识,也有一个标识种子和标识增量的设置,有 orm 框架时(比如 ef 什么的),在做migrate迁移时,也会自动创建。另外就是 mysql 也是只需要指定 auto_increment

3. PostgreSQL

终于说到本篇的主角了,为什么是主角,因为这是目前工作使用频率最高的数据库。博主曾一度以为 PostgreSQLmysql 这种数据库应该是差不了多少的,自增主键自然也不需要那么麻烦,由于工作是公司自研的ORM框架,也自带了Migrate功能,平时开发也是有点像.net以前热炒的概念 code first ,然后再执行迁移,也屏蔽了创建数据库的这块细节,让博主产生了这样的误解,进入到数据库管理工具发现,自增主键的实现依然依赖了序列( 当然,博主认为 mysqlsqlserver 底层也是有维护这样的一个序列 )。最近由于线上功能排序问题,应急修改了自增主键的值,它并不像 sqlserver 那样会直接抛错:

当 IDENTITY_INSERT 设置为 OFF 时,不能为表 ‘Db’ 中的标识列插入显式值。

只要没有任何索引限制, pg 都会允许更改。但是这样真的没问题吗?不,有问题,但是不大。

会有问题,问题在于如果修改的值已经大于序列的当前值,新增到下一行时,自增id的值刚好等于这个修改的值,就会违反某个索引约束,比如唯一索引,顺理成章的就会抛索引约束的错误。

**为什么问题又不大呢?**因为,序列的特点:每select一次,序列值就增加一次,新增下一列第一次失败,select了一次,再新增一次,就正常了。除非——-序列当前值的后面的一长串的id值都被占用了,那就从业务功能上操作上看,一直点新增,好像一直在抛错,相当不友好。怎么办?跟当年 oracle 一样:

# 1.先查看当前数据中自增id的最大值(max)
SELECT max(f_id) FROM table_name

# 2.检查当前表的序列值
select nextval('t_xxx_f_id_seq') # 这个值<max(f_id)

# 3.修改序列的初始值
alter sequence t_xxx_f_id_seq restart with [max(f_id)+1] # 比当前自增id最大值大1即可   

4. 问题扩展-索引名称

既然我们已经聊到了数据之间确实存在差异,我们就像摆龙门阵一样,摆到哪儿摆哪儿,讲一讲索引名称在 mysqlpgsql 之间的差异吧。当个笔记,这些差异虽然可以通过各种migrate工具抹平,但是底层却是实打实差异。

  • 创建索引的时候,MySQL 只要同表中索引名称唯一即可
  • PostgreSQL 必须整个库唯一

好了,就这么个问题,就捎带手记录一下,至于其他延展内容,请大家自行学习。