mysql序列(这里只谈innodb引擎)

  1. 在使用mysql的AUTO_INCREMENT时,使用AUTO_INCREMENT的字段必须建有索引,也可以为索引的一部分。当没有索引时会报错:          由于mysql是索引组织表,数据存放在主键中,又由于自增列字段内容长度较小,建议选用自增字段为主键。
  2. 对于mysql来说AUTO_INCREMENT 的加锁模式是由参数innodb_autoinc_lock_mode决定的,不同的加锁模式加上的不同的insert方式对mysql产生不同的影响,insert的类型包括:Simple inserts、Bulk inserts、Mixed-mode inserts三种。innodb_autoinc_lock_mode的取值有0,1,2三种。
    1. 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 with AUTO_INCREMENT columns。在这种锁模式下,所有的insert类型的语句,对于AUTO_INCREMENT列均加有特殊的表锁AUTO-INC锁
      • 这种锁模式在语句结束时结束,并非在事务结束时结束。这样可以保证每个auto-increment是在可预测,可重复的模式情况下赋值,也可以保证对于任何insert类语句,每个auto-increment列的赋值都是连续的。
      • 虽然这种锁模式下,可以保证auto-increment的连续性,但由于加的是表锁,所以对并发性影响较大。
    2. 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值,但分配的值是连续的。
    3. 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)将插入失败。

         

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  -j  -T  -f .sql
transaction type: Custom query
scaling factor:
query mode: simple
number of clients:
number of threads:
duration: s
number of transactions actually processed:
latency average: 0.124 ms
tps = 80482.444739 (including connections establishing)
tps = 87140.735942 (excluding connections establishing)
postgres@db-----> pgbench -n -c -j -T -f .sql
transaction type: Custom query
scaling factor:
query mode: simple
number of clients:
number of threads:
duration: s
number of transactions actually processed:
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  -j  -T  -f .sql
transaction type: Custom query
scaling factor:
query mode: simple
number of clients:
number of threads:
duration: s
number of transactions actually processed:
latency average: 1.467 ms
tps = 63938.069494 (including connections establishing)
tps = 161793.078271 (excluding connections establishing)
postgres@db-> pgbench -n -c -j -T -f .sql
transaction type: Custom query
scaling factor:
query mode: simple
number of clients:
number of threads:
duration: s
number of transactions actually processed:
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(, buf, REGBUF_WILL_INIT); /* set values that will be saved in xlog */
seq->last_value = next;
seq->is_called = true;
seq->log_cnt = ; 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

mysql 序列与pg序列的比较的更多相关文章

  1. r语言之生成规则序列,规则序列函数及用法

    在生成序列时,“:”的优先级最高 (1)从1到20的整数序列: > 1:20 [1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 (2) ...

  2. firedac二进制序列和JSON序列的对比

    firedac二进制序列和JSON序列的对比 以同样大小的一个数据集的数据,作为测试的数据. 以JSON序列后的数据体积是4958字节. 以二进制序列后的数据体积是3044字节. 4958/3044= ...

  3. Gym 101064 D Black Hills golden jewels 【二分套二分/给定一个序列,从序列中任意取两个数形成一个和,两个数不可相同,要求求出第k小的组合】

    D. Black Hills golden jewels time limit per test 2 seconds memory limit per test 256 megabytes input ...

  4. Python——序列封包与序列解包

    一.序列封包与序列解包 把多个值赋给一个变量时,Python会自动的把多个值封装成元组,称为序列封包. 把一个序列(列表.元组.字符串等)直接赋给多个变量,此时会把序列中的各个元素依次赋值给每个变量, ...

  5. 扩增子分析解读4去嵌合体 非细菌序列 生成代表性序列和OTU表

    本节课程,需要先完成 扩增子分析解读1质控 实验设计 双端序列合并 2提取barcode 质控及样品拆分 切除扩增引物 3格式转换 去冗余 聚类   先看一下扩增子分析的整体流程,从下向上逐层分析 分 ...

  6. Oracle和Mysql分别生成sequence序列

    有时候在往数据库中插入数据的时候,如果ID值是32位的UUID, 而自己随便写个字符又不合适,这时就要用到函数来产生一个序列值 Oracle: select sys_guid() from dual; ...

  7. [YY]已知逆序列求原序列(二分,树状数组)

    在看组合数学,看到逆序列这个概念.于是YY了一道题:已知逆序列,求出原序列. 例子: 元素个数 n = 8 逆序列 a={5,3,4,0,2,1,1,0} 则有原序列 p={4,8,6,2,5,1,3 ...

  8. 一个序列是否可能是push序列的pop序列

    输入:两个序列A.B,A作栈的push序列,判断B是否可能是A的pop序列. #include<iostream> #include<stack> using namespac ...

  9. 第29题:推断一个序列是否是还有一个push序列的pop序列

    github:https://github.com/frank-cq/MyTest 第29题:输入两个整数序列,当中一个序列表示栈的push顺序,推断还有一个序列有没有可能是相应的pop顺序.为了简单 ...

随机推荐

  1. SQL查询语句执行速度快,存储过程执行慢

    今天一个生成10w条数据的存储过程执行了95s,但是单独执行SQL语句只需要28s,查资料后发现原来这是存储过程的机制导致的,也就是传说中的参数嗅探 网上的一段话: (1)可能是发生了参数嗅探,第一次 ...

  2. Linux内核分析——理解进程调度时机跟踪分析进程调度与进程切换的过程

    20135125陈智威 +原创作品转载请注明出处 +<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 实验 ...

  3. centos6.3安装MySQL 5.6(转)

    1.下载地址:http://dev.mysql.com/downloads/mysql/5.6.html#downloads 选择“Source Code”, 用已经注册好的oracle账户登录后才能 ...

  4. 对Ajax的理解

    一.Ajax的工作原理: 1. Ajax的机制是:完成异步请求,实现页面的局部刷新. 2. 发送异步请求:通过xmlhttprequest方法. 3. 浏览器向服务器发送异步请求: 服务器接收处理请求 ...

  5. JS前台base32加密,C#后台解码

    公司的系统应用后,客户那边用appscan工具检测到严重的漏洞 1.使用 SQL 注入的认证旁路 (1/2)--未对用户输入正确执行危险字符清理 2.已解密的登录请求 (2/2)----诸如用户名.密 ...

  6. Unity jounal 2016-3-3

    1. Project organization: 2.prefab Important part of the project, which seperate and organize the scr ...

  7. Doherty Threshold

    Prior to the publication of the IBM technical paper behind what commonly known today as the Doherty ...

  8. Magento后台手动修改订单状态方法及手动修改方法php

    订单详细内容页手动修改订单状态方法: 打开此文件:app\design\adminhtml\default\default\template\sales\order\view\history.phtm ...

  9. Apache commons-client authentication(授权)

    import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.UsernamePasswo ...

  10. Spring3.0之后->Spring MVC过滤器-HiddenHttpMethodFilter

    浏览器form表单只支持GET与POST请求,而DELETE.PUT等method并不支持,spring3.0添加了一个过滤器,可以将这些请求转换为标准的http方法,使得支持GET.POST.PUT ...