mysql序列(这里只谈innodb引擎):
- 在使用mysql的
AUTO_INCREMENT时,使用
AUTO_INCREMENT的字段必须建有索引,也可以为索引的一部分。当没有索引时会报错: 由于mysql是索引组织表,数据存放在主键中,又由于自增列字段内容长度较小,建议选用自增字段为主键。
AUTO_INCREMENT 的加锁模式是由参数innodb_autoinc_lock_mode决定的,不同的加锁模式加上的不同的insert方式对mysql产生不同的影响,insert的类型包括:Simple inserts、Bulk inserts、Mixed-mode inserts三种。innodb_autoinc_lock_mode的取值有0,1,2三种。对于mysql来说
- innodb_autoinc_lock_mode=0时:
- In this lock mode, all “INSERT-like” statements obtain a special table-level
AUTO-INC
lock for inserts into tables withAUTO_INCREMENT
columns。在这种锁模式下,所有的insert类型的语句,对于AUTO_INCREMENT列均加有特殊的表锁AUTO-INC锁
- 这种锁模式在语句结束时结束,并非在事务结束时结束。这样可以保证每个auto-increment是在可预测,可重复的模式情况下赋值,也可以保证对于任何insert类语句,每个auto-increment列的赋值都是连续的。
- 虽然这种锁模式下,可以保证auto-increment的连续性,但由于加的是表锁,所以对并发性影响较大。
- In this lock mode, all “INSERT-like” statements obtain a special table-level
- innodb_autoinc_lock_mode =1 时:
- 在该模式下,bulk inserts添加的
AUTO-INC表锁,并一直持续到语句结束。这是适用于
INSERT ... SELECT
,REPLACE ... SELECT和
LOAD DATA语句。
- 对于事前知道插入多少行的Simple inserts,则可以避免AUTO-INC表锁,它加的是轻量级的锁mutex ,只会在自增列值分配时存在,不会等到语句执行结束。
- 对于Mixed-mode inserts,innodb会分配比插入的行更多的auto-increment值,但分配的值是连续的。
- 在该模式下,bulk inserts添加的
- innodb_autoinc_lock_mode =2 时:
- 在这种锁模式下,对于所有insert 类型的语句都不会加AUTO-INC表锁,这是最快和最具伸缩性的模式。但是当在binlog是statement格式下时,这种模式是不安全的。
- 在这种锁模式下,auto-increment值可以保证是唯一和单调的递增的,即使存在并发的insert类型操作时,auto-increment值不连续的情况。
3、(1)当在statement-based复制下时,innodb_autoinc_lock_mode为0或1,均可以保证主从序列的一致性。当配置的innodb_autoinc_lock_mode=2或是主从不一样时,则主从序列值不能保证一致。
(2)如果使用的是row-based 或 mixed-format复制,则auto-increment的锁模式都是安全的,因为基于row-based的复制是不受sql语句执行顺序的影响的。
(3)如果在事务中含有auto-increment的分配语句回滚了,那分配的auto-increment值也就消失了,不能再被使用。auto-increment值是不会被回滚的。
(4)当在AUTO_INCREMENT列插入的值是NULL或0时,
AUTO_INCREMENT都会分配一个新值。
(5)当插入的值是负值时,也可以正常插入,由于mysql是索引组织表,插入的负值会插在其他正值的前面.
(6)当做bulk inserts操作,且innodb_autoinc_lock_mode为0或1此时AUTO_INCREMENT值不会产生间隙,因为此时添加的是表锁。innodb_autoinc_lock_mode=2时,则可能产生间隙。
(7)对于mixed-mode inserts,innodb_autoinc_lock_mode不同的值会有不同的情况:
下面是创建的测试表:
该表最新自动产生的序列值是100:
mysql> CREATE TABLE t1 ( -> c1 INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, -> c2 CHAR(1) -> ) ENGINE = INNODB; mysql> INSERT INTO t1 (c1,c2) VALUES (1,'a'), (NULL,'b'), (5,'c'), (NULL,'d');
-
-
- 当innodb_autoinc_lock_mode=0时:
mysql> SELECT c1, c2 FROM t1 ORDER BY c2; +-----+------+ | c1 | c2 | +-----+------+ | 1 | a | | 101 | b | | 5 | c | | 102 | d | +-----+------+
后面自动分配的将是103,因为auto-increment值一次分配一个,而不是分配了所有的值。无论是否并发,结果均如此。
- 当innodb_autoinc_lock_mode=1时:
mysql> SELECT c1, c2 FROM t1 ORDER BY c2; +-----+------+ | c1 | c2 | +-----+------+ | 1 | a | | 101 | b | | 5 | c | | 102 | d | +-----+------+
后面自动分配的将是105,不是103,这是因为在语句执行时,就已将4个自增值分配了,而不仅仅是使用到的两个值。无论是否并发,结果均如此。
- 当innodb_autoinc_lock_mode=2时:
mysql> SELECT c1, c2 FROM t1 ORDER BY c2; +-----+------+ | c1 | c2 | +-----+------+ | 1 | a | | x | b | | 5 | c | | y | d | +-----+------+
在分配时,x和y将是惟一的,而且比前面的值要大,然后这里的x和y值将受到并发insert操作的影响。假如当前序列分配的值是4,则x将为5,与后面的5值相同,报错,后面的(5,c)将插入失败。
- 当innodb_autoinc_lock_mode=0时:
-
pg序列:
下面是pg创建序列的语法:
CREATE [ TEMPORARY | TEMP ] SEQUENCE name [ INCREMENT [ BY ] increment ] [ MINVALUE minvalue | NO MINVALUE ] [ MAXVALUE maxvalue | NO MAXVALUE ] [ START [ WITH ] start ] [ CACHE cache ] [ [ NO ] CYCLE ] [ OWNED BY { table_name.column_name | NONE } ]
序列中各参数具体含义请参考官方文档,这里主要讲解几个参数:
cache:
cache是指预分配几个序列值,放在内存中,以方便序列的快速分配。没有指定时,默认值是1.当该值设置大于1时,多个session同时做高并发insert时,每个session都会预分配cache个序列值,但不见得每个预分配的序列值会被使用,所以会出现序列值不连续的情况。不过pg序列已专门做过优化,cache加大效果不大,后面会有介绍。
CYCLE:
这个参数用于,当序列值递增至最大值或递减(当increment为负值时)至最小值时,是否循环从设定的最大值或最小值开始。pg序列默认的最大值是:9223372036854775807,pg序列默认是基于bigint类型创建的。当超过该值并且没有设定CYCLE时,会报错。默认是NO CYCLE.
另外需要注意的时,在使用setval重设序列值时,其他session将不会受影响,直到cache预分配的值使用完毕后。
测试CYCLE对序列的影响:
创建一个序列:
选项均使用默认值。看到is_cycled是f,默认是NO CYCLE的。
swrd=# create sequence seq1; CREATE SEQUENCE swrd=# d seq1 Sequence "swrd.seq1" Column | Type | Value ---------------+---------+--------------------- sequence_name | name | seq1 last_value | bigint | 1 start_value | bigint | 1 increment_by | bigint | 1 max_value | bigint | 9223372036854775807 min_value | bigint | 1 cache_value | bigint | 1 log_cnt | bigint | 0 is_cycled | boolean | f is_called | boolean | f
将该序列修改成递减,并将当前值修改5.
swrd=# alter sequence seq1 increment -1; ALTER SEQUENCE swrd=# select setval('seq1',5); setval -------- 5 (1 row) swrd=# d seq1 Sequence "swrd.seq1" Column | Type | Value ---------------+---------+--------------------- sequence_name | name | seq1 last_value | bigint | 5 start_value | bigint | 1 increment_by | bigint | -1 max_value | bigint | 9223372036854775807 min_value | bigint | 1 cache_value | bigint | 1 log_cnt | bigint | 0 is_cycled | boolean | f is_called | boolean | t
下面不断的取下一个序列值:
swrd=# select nextval('seq1'); nextval --------- 4 (1 row) swrd=# select nextval('seq1'); nextval --------- 3 (1 row) swrd=# select nextval('seq1'); nextval --------- 2 (1 row) swrd=# select nextval('seq1'); nextval --------- 1 (1 row) swrd=# select nextval('seq1'); ERROR: nextval: reached minimum value of sequence "seq1" (1)
当取到1时,由于不是循环的,最后报错,达到默认的最小值。
现将序列修改为CYCLE,然后取下一个值:
swrd=# alter sequence seq1 cycle; ALTER SEQUENCE swrd=# d seq1 Sequence "swrd.seq1" Column | Type | Value ---------------+---------+--------------------- sequence_name | name | seq1 last_value | bigint | 1 start_value | bigint | 1 increment_by | bigint | -1 max_value | bigint | 9223372036854775807 min_value | bigint | 1 cache_value | bigint | 1 log_cnt | bigint | 0 is_cycled | boolean | t is_called | boolean | t swrd=# select nextval('seq1'); nextval --------------------- 9223372036854775807 (1 row) swrd=# select nextval('seq1'); nextval --------------------- 9223372036854775806 (1 row)
设定循环后,可以继续往下取值,不会报错。递增的情况也与此类似。所以对数据库序列的定期巡检是有必要的,对于快达到最小或最大值的序列要及时处理,以免影响业务访问。
sequence与serial:
serial并不是pg的数据类型,但它可以用来方便的创建序列。
下面的两种方式是等价的:
使用serial:
swrd=# create table test_serial(id serial); CREATE TABLE swrd=# d test_serial Table "swrd.test_serial" Column | Type | Modifiers --------+---------+---------------------------------------------------------- id | integer | not null default nextval('test_serial_id_seq'::regclass) swrd=# d test_serial_id_seq Sequence "swrd.test_serial_id_seq" Column | Type | Value ---------------+---------+--------------------- sequence_name | name | test_serial_id_seq last_value | bigint | 1 start_value | bigint | 1 increment_by | bigint | 1 max_value | bigint | 9223372036854775807 min_value | bigint | 1 cache_value | bigint | 1 log_cnt | bigint | 0 is_cycled | boolean | f is_called | boolean | f Owned by: swrd.test_serial.id
使用sequence:
swrd=# create sequence test_seq_id ; CREATE SEQUENCE swrd=# create table test_seq(id int not null default nextval('test_seq_id')); CREATE TABLE swrd=# alter sequence test_seq_id owned by test_seq.id; ALTER SEQUENCE swrd=# d test_seq Table "swrd.test_seq" Column | Type | Modifiers --------+---------+--------------------------------------------------- id | integer | not null default nextval('test_seq_id'::regclass) swrd=# d test_seq_id Sequence "swrd.test_seq_id" Column | Type | Value ---------------+---------+--------------------- sequence_name | name | test_seq_id last_value | bigint | 1 start_value | bigint | 1 increment_by | bigint | 1 max_value | bigint | 9223372036854775807 min_value | bigint | 1 cache_value | bigint | 1 log_cnt | bigint | 0 is_cycled | boolean | f is_called | boolean | f Owned by: swrd.test_seq.id
注意:
1、在创建外部表时,当主表存在含有序列的列,则外部表创建时,可以使用sequence创建序列,但不能使用serial创建。这个问题会在另一篇博客中讲解。
2、当使用serial创建的id列,删除时,创建时对应的序列也会一并删除,但单独使用sequence创建的序列则不会。
3、但不论是使用哪种方式创建的序列,当删除序列时,都会提示存在依赖关系,提示使用CASCADE一并将依赖对象删除。要想不删除依赖的表,可以将default值去掉。然后单独删除序列。
测试cache对序列的影响:
这里创建两个序列一个cache使用默认值1,另一个序列将cache值设为100.
postgres=# create sequence seq1; CREATE SEQUENCE postgres=# create sequence cached_seq cache 100; CREATE SEQUENCE
测试10、100个并发时的情况:
1.sql文件中的内容是:select nextval('seq1'),2.sql文件中的内容是:select nextval('cached_seq')
10个并发时:
postgres@db-> pgbench -n -c 10 -j 10 -T 2 -f 1.sql transaction type: Custom query scaling factor: 1 query mode: simple number of clients: 10 number of threads: 10 duration: 2 s number of transactions actually processed: 160990 latency average: 0.124 ms tps = 80482.444739 (including connections establishing) tps = 87140.735942 (excluding connections establishing) postgres@db-192-168-173-43-> pgbench -n -c 10 -j 10 -T 2 -f 2.sql transaction type: Custom query scaling factor: 1 query mode: simple number of clients: 10 number of threads: 10 duration: 2 s number of transactions actually processed: 168619 latency average: 0.119 ms tps = 84295.675509 (including connections establishing) tps = 90538.479485 (excluding connections establishing)
10个并发时,cache使用默认值tps为87140.735942,cache值为100的tps是:90538.479485,两者相差不大。
100个并发时:
postgres@db-> pgbench -n -c 100 -j 100 -T 2 -f 1.sql transaction type: Custom query scaling factor: 1 query mode: simple number of clients: 100 number of threads: 100 duration: 2 s number of transactions actually processed: 136312 latency average: 1.467 ms tps = 63938.069494 (including connections establishing) tps = 161793.078271 (excluding connections establishing) postgres@db-> pgbench -n -c 100 -j 100 -T 2 -f 2.sql transaction type: Custom query scaling factor: 1 query mode: simple number of clients: 100 number of threads: 100 duration: 2 s number of transactions actually processed: 162665 latency average: 1.230 ms tps = 80642.482598 (including connections establishing) tps = 163479.402068 (excluding connections establishing)
100个并发时,默认cache值时的tps为:161793.078271,cache为100的tps为:163479.402068,两者相差更小了。
注:
1、按照对cache选项的理解,cache越大,性能越高才对,但测试发现cache对性能影响不大。
2、既然cache对序列的性能提高不大,那尽量cache使用默认值1,以避免出现序列值的不连续性。
3、pg序列的如此高效性,接近redis的INCRs。具体可参考Don’t Assume PostgreSQL is Slow。
pg序列可以这么快及cache作用不大的原因:
在pg源码src/backend/commands/sequence.c中可以看到:
/* * We don't want to log each fetching of a value from a sequence, * so we pre-log a few fetches in advance. In the event of * crash we can lose (skip over) as many values as we pre-logged. */ #define SEQ_LOG_VALS 32
/*
* Decide whether we should emit a WAL log record. If so, force up the * fetch count to grab SEQ_LOG_VALS more values than we actually need to * cache. (These will then be usable without logging.) * * If this is the first nextval after a checkpoint, we must force a new * WAL record to be written anyway, else replay starting from the * checkpoint would fail to advance the sequence past the logged values. * In this case we may as well fetch extra values. */ if (log < fetch || !seq->is_called) { /* forced log to satisfy local demand for values */ fetch = log = fetch + SEQ_LOG_VALS; logit = true; } else { XLogRecPtr redoptr = GetRedoRecPtr(); if (PageGetLSN(page) <= redoptr) { /* last update of seq was before checkpoint */ fetch = log = fetch + SEQ_LOG_VALS; logit = true; } }
/* * We don't log the current state of the tuple, but rather the state * as it would appear after "log" more fetches. This lets us skip * that many future WAL records, at the cost that we lose those * sequence values if we crash. */ XLogBeginInsert(); XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); /* set values that will be saved in xlog */ seq->last_value = next; seq->is_called = true; seq->log_cnt = 0; xlrec.node = seqrel->rd_node; XLogRegisterData((char *) &xlrec, sizeof(xl_seq_rec)); XLogRegisterData((char *) seqtuple.t_data, seqtuple.t_len); recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG); PageSetLSN(page, recptr);
} /* Now update sequence tuple to the intended final state */ seq->last_value = last; /* last fetched number */ seq->is_called = true; seq->log_cnt = log; /* how much is logged */
可以看到pg中并不是每产生一个序列值就做一次记录wal log的操作,而是每产生32个序列值后才会记一次wal log record。所以这相当于已对序列做了缓存。
其中log_cnt扮演者计数器的角色,当该值减为0时,则会产生一个记录wal log的标志位。该计数器对应的是序列属性中的log_cnt。
swrd=# d seq1 Sequence "swrd.seq1" Column | Type | Value ---------------+---------+--------------------- sequence_name | name | seq1 last_value | bigint | 9223372036854775806 start_value | bigint | 1 increment_by | bigint | -1 max_value | bigint | 9223372036854775807 min_value | bigint | 1 cache_value | bigint | 1 log_cnt | bigint | 31 is_cycled | boolean | t is_called | boolean | t
每产生一个序列值该值就减1。但当服务器意外宕机时,则会丢掉代码中缓存的序列值。
下面做个测试:
当前序列seq的值为15:
swrd=# select nextval('seq1'); nextval --------- 15 (1 row) swrd=# d seq1 Sequence "swrd.seq1" Column | Type | Value ---------------+---------+--------------------- sequence_name | name | seq1 last_value | bigint | 15 start_value | bigint | 1 increment_by | bigint | 1 max_value | bigint | 9223372036854775807 min_value | bigint | 1 cache_value | bigint | 1 log_cnt | bigint | 32 is_cycled | boolean | t is_called | boolean | t
它的下个值应为16,而且还有32个值没有分配,此时模拟数据库意外宕机,将数据库的进程强制杀掉:
swrd=# d seq1 Sequence "swrd.seq1" Column | Type | Value ---------------+---------+--------------------- sequence_name | name | seq1 last_value | bigint | 47 start_value | bigint | 1 increment_by | bigint | 1 max_value | bigint | 9223372036854775807 min_value | bigint | 1 cache_value | bigint | 1 log_cnt | bigint | 0 is_cycled | boolean | t is_called | boolean | t swrd=# select nextval('seq1'); nextval --------- 48 (1 row)
强制杀掉pg后,再启动发现当前序列的值是47,及缓存的32加上之前的序列值15,再执行nextval,下一个序列值是48.
pg迁移时的序列:
当进行pg迁移时可能存在序列字段恰好是主键的表的情况,迁移完成后,一定要检查有无这种情况,将序列值设置为主键值的max值,如果产生的序列值小于主键max值的情况,则数据将插入不进去。
pg与mysql序列比较:
1、pg的序列属性是基于单表,而mysql则是基于整个实例的。
2、pg的序列不必建有主键,而mysql多为建有主键索引,所以不存在重复的情况,而pg则会存在。
3、pg的序列高并发时效率非常高,不会出现锁表的情况,而mysql则要视insert的类型、参数innodb_autoinc_lock_mode的值和binlog的格式设定情况来看。
4、pg中可以限定序列值达到限定的最大值或最小值时CYCLE使用,mysql则可以通过修改字段类型为unsigned或是将字段类型更改为更大范围的类型,没有看到CYCLE的用法。
5、pg中序列值可递减,而mysql中序列值开始值和增加值均必须为正数,范围均为1 .. 65535。由两个参数控制:auto_increment_offset控制从哪个值开始,auto_increment_increment:控制每次递增的量。
6、pg的序列与mysql的序列均不能回滚。
7、pg的序列字段不一定要创建索引,但mysql则必须创建。
参考:
https://dev.mysql.com/doc/refman/5.7/en/innodb-auto-increment-handling.html
https://dev.mysql.com/doc/refman/5.7/en/example-auto-increment.html
https://www.postgresql.org/docs/9.4/static/sql-createsequence.html
https://www.postgresql.org/docs/9.4/static/sql-altersequence.html
https://www.postgresql.org/docs/9.4/static/functions-sequence.html
https://www.postgresql.org/docs/9.4/static/datatype-numeric.html
http://blog.chinaunix.net/uid-20726500-id-4727557.html?spm=5176.100239.blogcont.6.DBlloS
src/backend/commands/sequence.c
https://yq.aliyun.com/articles/6053?do=login