WL#827: SEQUENCE object as in Oracle, PostgreSQL, and/or SQL:2003
Affects: Server-7.1
—
Status: Un-Assigned
In Oracle, PostgreSQL, and in SQL:2003, there are SEQUENCE objects which have various options (to specify what the first number is, what the increment value is, whether a range of numbers is cached, etc.). Sequences are separate objects rather than part of tables. Sequence values are dissociated from table storage, therefore deletes do not affect them and rollbacks do not affect them. Oracle implementation: http://docs.oracle.com/cd/E16655_01/server.121/e17209/statements_6016.htm#SQLRF01314 PostgreSQL implementation: http://www.postgresql.org/docs/9.3/static/sql-createsequence.html An article "Sequences and Identity Columns" (http://web.archive.org/web/20021201224701/www.dbazine.com/gulutzan4.html) describes the matter more fully. I added this item at Sinisa's request. In the Bordeaux group leaders' meeting (November 2003), "Oracle-type sequences" was specified as an item for version 5.1.
CREATE SEQUENCE --------------- Syntax: CREATE SEQUENCE sequence_name [option [,option ...]]; The sequence_name is like other identifiers: maximum length 64, stored in UTF-8, qualifiable with catalog.schema, case insensitive, and not the same as any existing sequence in the same schema (i.e. in the same database). Related to dev-private discussion "Identifier equivalence and events": "Identifiers are equivalent if their upper-case forms are equal according to a binary collation with space padding." An option may be any one of these, in any order: AS data_type INCREMENT BY increment MAXVALUE maximum_value | NO MAXVALUE MINVALUE minimum_value | NO MINVALUE START WITH start_value CYCLE | NO CYCLE CACHE cache_value | NO CACHE NO ORDER No option may be specified more than once. The "NO ORDER" clause exists for compatibility reasons. Ignore it. Initially, accept any signed big integer for increment, maximum_value, minimum_value, start_value, and cache_value. For "AS data_type": allowable values are any one of TINYINT, SMALLINT, MEDIUMINT, INT | INTEGER, BIGINT, TINYINT UNSIGNED, SMALLINT UNSIGNED, MEDIUMINT UNSIGNED, INT | INTEGER UNSIGNED, BIGINT UNSIGNED, DECIMAL(n), or NUMERIC(n). DEFAULT is BIGINT. (The default is implementation-defined, if somebody thinks INTEGER or SMALLINT is better, okay.) In reality we should store everything as BIGINT; however, the data type specified here will affect the calculation of default min/max values (see next paragraph), and the data type of the function that retrieves the next sequence value (see later section titled "NEXT VALUE FOR"). After all options are parsed, set default values and check for errors with these steps, in order: 1. Given the data type, calculate Default_min_value and Default_max_value according to the range limits specified in the MySQL Reference Manual http://www.mysql.com/doc/en/Column_types.html For example if the specification was "AS SMALLINT" then the data type is SMALLINT, the Default min_value = -32768, and the Default max_value = 32767. 2. If "NO MAXVALUE" was specified, or if no MAXVALUE clause was specified, then maximum_value = Default_maximum_value. 3. If "NO MINVALUE" was specified, or if no MINVALUE clause was specified, then minimum_value = Default_minimum_value. 4. If no INCREMENT clause was specified, then increment = 1. 5. If no START WITH clause was specified, then if increment < 0 start_with = maximum_value, else (if increment > 0) start_with = minimum_value. 6. If no CYCLE clause was specified, then it's 'NO CYCLE'. 7. If "NO CACHE" was specified, or if no CACHE clause was specified, then cache_value = 20. 8. The following are syntax errors (return sqlstate = '42000'): - if maximum_value > Default_maximum_value - if minimum_value < Default_minimum_value - if minimum_value >= maximum_value - if start_value < minimum_value or start_value > maximum_value - if increment = 0 or abs(increment) > (max_value - min_value) - if cache_value <= 0 Privileges required: Not yet known. We have CREATE VIEW and CREATE ROUTINE privileges, so doubtless we should have a CREATE SEQUENCE privilege. Oracle alternate syntax: Oracle has NOMAXVALUE for NO MAXVALUE, NOMINVALUE for NO MINVALUE, NOCYCLE for NO CYCLE. Let's not do that. Correctly speaking, the thing that CREATE SEQUENCE creates is not a "sequence", but a "sequence generator". Peter Gulutzan finds it hard to remember to say "sequence generator" but we should all try. This is an example, using all options but with default values: CREATE SEQUENCE sequence1_seq AS BIGINT INCREMENT BY 1 MAXVALUE 9223372036854775807 MINVALUE -9223372036854775808 START WITH -9223372036854775808 NO CYCLE CACHE 20 NO ORDER mysql.sequences --------------- A sequence generator is a different object from the ones we already have (users, tables, routines). And sequence generators have their own namespace. And they are within a schema. Peter proposes this structure because most of the column names are like what we'll need in information_schema.sequences: sequence_catalog varchar(64) i.e. catalog (depends on WL#942) sequence_schema varchar(64) i.e. schema. don't call it `db` sequence_name varchar(64) data_type varchar(64) e.g. 'UNSIGNED SMALLINT'. or a number. numeric_precision smallint e.g. 8 for DECIMAL(8) maximum_value bigint minimum_value bigint increment bigint cycle_option varchar(3) 'YES' or 'NO', 'YES' means 'CYCLE' start_value bigint cache_value integer current_base_value bigint this should initially be NULL There will be additional columns for internal sequence generators, associated with columns. CREATE SEQUENCE should insert a row in this table. DROP SEQUENCE should delete a row in this table. ALTER SEQUENCE should update a row in this table. For precedents, see what we already do for create/drop/alter procedure and mysql.proc. DROP SEQUENCE ------------- Syntax: DROP SEQUENCE sequence_name; The standard actually requires "DROP SEQUENCE sequence_name CASCADE|RESTRICT". However, since Peter expects we won't actually support cascade|restrict, and it's a sin to lie, we cannot use the cascade|restrict clause. Privileges required: DROP. If somebody thinks it should be a special DROP SEQUENCE privilege, that's okay too. ALTER SEQUENCE -------------- Syntax: ALTER SEQUENCE sequence_name [option [,option ...]]; The possible options are the same as for CREATE SEQUENCE, and the restrictions are the same as for CREATE SEQUENCE. Except for one clause: Instead of START WITH start_value, one must say RESTART WITH start_value. SQL:2011 non-core feature T177 Sequence generator support: simple restart option allows ALTER SEQUENCE ... RESTART ... NEXT VALUE FOR: Syntax ---------------------- Syntax: NEXT VALUE FOR schema_name You may use NEXT VALUE for in a SELECT list, for example SELECT NEXT VALUE FOR catalog1 . schema1 . special_seq; The SELECT must not contain DISTINCT (this restriction seems unnecessary, lift it if you wish). You may use NEXT VALUE FOR as a source in an UPDATE statement, for example UPDATE t SET column1 = NEXT VALUE FOR schema1.special_seq; You may use NEXT VALUE FOR for an INSERT statement, for example INSERT INTO t VALUES (NEXT VALUE FOR special_seq); And that's all. You may not use NEXT VALUE FOR anywhere else. For example, these statements are illegal: SELECT * FROM t WHERE column1 = NEXT VALUE FOR special_seq; SET @a = NEXT VALUE FOR special_seq; You must have USAGE privilege on the sequence (see "Privilege" below). MySQL should evaluate a NEXT VALUE FOR expression once per selected row. For example: CREATE TABLE t1 (s1 INT); INSERT INTO t1 VALUES (1),(2); CREATE SEQUENCE seq ... SELECT s1, NEXT VALUE FOR seq, NEXT VALUE FOR seq FROM t1; The result will be: {1, 1, 1} {2, 2, 2} NEXT VALUE FOR: Simple Rules ---------------------------- For any given sequence ... (words in CAPITALS are column names in mysql.sequence) if CURRENT_BASE_VALUE is null { /* current_base_value is null only after "create/alter sequence", so we get here only if we have never called "next value" before */ set CURRENT_BASE_VALUE = START_VALUE return (CURRENT_BASE_VALUE) } set x = CURRENT_BASE_VALUE + INCREMENT (beware of overflow) if (INCREMENT > 0 and x > MAX_VALUE) or (INCREMENT < 0 AND x < MIN_VALUE) { if (CYCLE_OPTION = 'no') { return error: sqlstate = '22003' message = "data exception: sequence generator limit exceeded" } if (CYCLE_OPTION = 'yes') { if (INCREMENT > 0) set CURRENT_BASE_VALUE = MIN_VALUE if (INCREMENT < 0) set CURRENT_BASE_VALUE = MAX_VALUE set x = CURRENT_BASE_VALUE + INCREMENT } } return (x) PROBLEM: Whenever you make a change to CURRENT_BASE_VALUE, you are changing a row in the mysql.sequences table, so a lock|read|write|unlock process may be necessary. Of course fsync would be good too. NEXT VALUE FOR: Reducing The Problem ------------------------------------ This section is not what ANSI/ISO requires. It is what others do, with modifications that (Peter thinks) could suit MySQL. The problem described in the last section ("NEXT VALUE FOR: Simple Rules") can be reduced in one of two ways: 1. Do not store CURRENT_BASE_VALUE in mysql.sequences. Store it in the log. Although this is feasible for InnoDB there is no guarantee that we can do it at all times, so let's look at this as a (very good) enhancement option. 2. Do not lock|read|write|unlock the mysql.sequences row every time that NEXT VALUE FOR happens. Instead, get a batch of numbers. Let's concentrate solely on the second solution. For every session, there is a "local copy" of every sequence that the session has used (i.e. has called NEXT VALUE FOR for). The idea is that we will keep a "cache" of N numbers available in memory. There is also, for each local copy, a bigint named used_number_count. When NEXT VALUE FOR happens: 1. If there is no local copy of the sequence, make a local copy and set used_number_count = CACHE_SIZE. 2. If used_number_count = CACHE_SIZE, do "The Grab". That is, Perform "NEXT VALUE FOR: Simple Rules" as described above. But instead of using INCREMENT, use INCREMENT * CACHE_VALUE. So the revision of the "Simple Rules" looks like this: if {used_number_count == 0) { if CURRENT_BASE_VALUE is null { /* current_base_value is NULL only after "create/alter sequence", so we get here only if we have never called "next value" before */ set x = CURRENT_BASE_VALUE set CURRENT_BASE_VALUE = START_VALUE + (CACHE_VALUE * (INCREMENT - 1)) return (x) } set x = CURRENT_BASE_VALUE + INCREMENT * CACHE_VALUE (beware of overflow) if (INCREMENT > 0 and x > MAX_VALUE) or (INCREMENT < 0 AND x < MIN_VALUE) { if (CYCLE_OPTION = 'no') { return error: sqlstate = '22003' message = "data exception: sequence generator limit exceeded" } if (CYCLE_OPTION = 'yes') { if (INCREMENT > 0) set CURRENT_BASE_VALUE = MIN_VALUE if (INCREMENT < 0) set CURRENT_BASE_VALUE = MAX_VALUE set x = CURRENT_BASE_VALUE + INCREMENT set CURRENT_BASE_VALUE = CURRENT_BASE_VALUE + INCREMENT * CACHE_SIZE } } return (x) } Now we have x, which is the first useful "NEXT VALUE FOR" value. Put this in the local copy, as (local)CURRENT_BASE_VALUE. Also, set used_number_count = 0. And now, to return. set return_value = (local)CURRENT_BASE_VALUE + (INCREMENT * used_number_count); set used_number_count = used_number_count + 1; return (return_value); To sum it up: if CACHE_SIZE is 20, then there will be an access of the mysql.sequences row only once for every twenty calls to NEXT VALUE FOR. The other nineteen times, the access will be of a local unlocked in-memory copy of the mysql.sequences row. When the session ends, there will almost certainly be some numbers still "in the cache". They are lost. They will never be used. So you probably won't have a dense series of numbers unless you specify "CREATE SEQUENCE ... CACHE 1". But generally you don't need a dense series, and generally it's acceptable to lose 20 numbers when you have 18,446,744,073,709,551,615 available. For another, possibly simpler, description of the whole technique, see the article "Sequences and Identify Columns" in DBAZINE.com: http://web.archive.org/web/20021201224701/www.dbazine.com/gulutzan4.html. CURRVAL and PREVVAL ------------------- This is a non-standard extension. Oracle uses CURRVAL, DB2 uses PREVVAL. They mean the same thing. SELECT PREVVAL FOR sequence_name; /* DB2 style */ SELECT sequence_name.CURRVAL; /* Oracle style */ This function will retrieve the local copy. So if there is no local copy (because the session never called NEXT VALUE FOR), the result is meaningless, return NULL. This is similar to LAST_INSERT_ID(), so perhaps some people would prefer to see LAST_INSERT_ID (sequence_name). Peter doesn't. Privilege --------- In order to use a sequence generator, you need the USAGE privilege on it. To grant a usage privilege, say: GRANT USAGE ON SEQUENCE name TO user-list [WITH GRANT OPTION]; To revoke a usage privilege, say: REVOKE USAGE ON SEQUENCE name FROM user-list; The MySQL extensions like "GRANT|REVOKE USAGE ON database_name.*" as described in the MySQL Reference Manual, http://www.mysql.com/doc/en/GRANT.html are okay. New reserved words ------------------ New reserved words are: CYCLE, START. WITH is already a reserved word. INCREMENT, MAXVALUE, MINVALUE, SEQUENCE are not new reserved words. SHOW ---- For other new object types that we've introduced into MySQL, we've added support for corresponding SHOW CREATE statements. For example SHOW CREATE TRIGGER, SHOW CREATE EVENT. So support: SHOW CREATE SEQUENCE sequence_name; This will show a CREATE SEQUENCE statement that would reproduce the current object identified by sequence_name. See also WL#3574 "Online backup: SHOW commands for various metadata" and WL#3582 "Online Backup: Add SHOW commands for needed metadata". There will not be a SHOW SEQUENCES statement. To get metadata about sequences, there should be an information_schema table. The description of information_schema.sequences may be a separate task. Meanwhile one can use mysql.sequences. You need a privilege on sequence_name in order to access its metadata. The privilege could be either USAGE or CREATE SEQUENCE. Replication ----------- There could be some complications with replication if we used a log to store CURRENT_BASE_VALUE, but that's not the initial plan. There certainly is a problem with NEXT VALUE FOR if one uses the binary log. Once again, we have a function value that must be passed for every statement. Compatibility ------------- Once we have all the details in WL#827, we can claim support for SQL:2003 feature T176 "Sequence generator support". We won't guarantee that no numbers will be lost, and it's possible that they won't be in temporal order. The CACHE clause and the NEXTVAL/CURRVAL function are extensions used by Oracle and DB2. DB2 would also allow an INCREMENT size of 0, we don't. There are slight differences in the CREATE SEQUENCE syntax, e.g. placement of commas or use of NOMINVALUE instead of NO MINVALUE, but the required change should be obvious to any user. The same is true for the function NEXT VALUE FOR, which has the spelling NEXTVAL FOR in DB2. auto_increment -------------- We won't depend on MySQL's auto_increment feature. Peter's hope is that eventually we can throw the auto_increment feature away. References ---------- WL#1295 "Implement AUTO_DECREMENT" -- will be unnecessary if we do WL#827 WL#1327 "DB2-type IDENTITY columns" -- could be made to depend on sequences BUG#1625 Built in sequence support (Feature Request) People have asked about CREATE SEQUENCE several times in MySQL Forums: http://forums.mysql.com/read.php?10,147719,147719 http://lists.mysql.com/mysql/178439 http://forums.mysql.com/read.php?61,113934,113934 http://forums.mysql.com/read.php?10,127559,127559
Copyright (c) 2000, 2024, Oracle Corporation and/or its affiliates. All rights reserved.