The series so far:
The final installment of this mini-series on sequences looks at the new features relating to sequences that appeared in the 12c timeline in response, possibly, to some of the more frequently voiced complaints of users and developers. In particular, the article shows how it is now possible to use synthetic keys for a table through a sequence that is implicitly created and tied to that table by a static declaration.
Feature round-up
Since Oracle 12.2, and all the way up to 21c, the complete syntax of the create
sequence
command (excluding only the details relating to creating sequences that can be shared from an application root to its application PDBs) has been as follows:
There is a new option for altering a sequence that appeared in 12.1 but was not documented until 18c:
alter SEQUENCE [schema_name.]{sequence_name} restart;
The significant new feature that goes along with sequences, though, is that you can (at last!) define one column in a table to be an identity column. Having an identity column in a table means a sequence will be created and linked to the column behind the scenes so that inserts into the table will automatically use the sequence nextval
to populate that column when needed.
Basic options
Of the four newer options highlighted above for create sequence
, I’ve covered the scale/extend
option in an earlier installment discussing RAC. If you specify scale
, the sequence generator uses six leading digits to include the instance and session information. Unless you include the extend
option, these six digits will be considered as contributing to your stated maxvalue
– which means your effective range of values has been reduced by a factor of a million.
The shard
option does for sharded databases what the scale
option does for RAC instances. It uses the four leading digits of the generated value for the shard id – presumably to allow you to avoid cross-shard updates taking place when sequence values are used in indexes. As with scale, shard can include the extend option, but if you use both scale and shard, then extend needs only appear once in the identity declaration.
It’s worth noting that the manuals advise you against using the order
option if you’ve used the scale
or shard
options. Since their purpose is to minimize cross instance and cross shard calls, this makes perfect sense as the order option would result in the instances/shards constantly communicating with each other to pass the current high value around.
The keep
option is a detail you need to know about if you’re implementing Oracle’s Application Continuity features. Since nextval
(like sysdate
, systimestamp
, and a couple of others) is not a transactional value, Oracle needs to do something special to ensure that transactions can be replayed correctly in the event of an outage. To allow this to happen, the sequence needs to be declared with the keep option, and application users need the keep sequence privilege, granted through a command like:
grant keep sequence on {sequence name} to {user name};
Finally, there is the global/session
option. The default is global
, which behaves in the way that create sequence
has always behaved. The session option means the sequence will be a “temporary” sequence (rather like a global temporary table). When a session selects nextval
from a session sequence, it will create a private copy of the sequence from the sequence metadata and work only with that private copy. If multiple sessions are using the same session sequence, they will all have their own private copies and all start at the same starting value without interfering with each other.
There are several benefits to session sequences when it’s appropriate to use them). You no longer have to worry about updates to the seq$
table (which is particularly convenient for RAC systems), and you can now use sequences on read-only databases (provided the database was read-write at some point so that you could create the sequence).
This feature may be particularly relevant to systems using Active Data Guard. With a suitable sequence definition at the primary database, a standby database can request a range (dictated by the cache size) of values from the primary that will not duplicate values that will be used by the primary. If you can define the sequence as a session sequence at the primary database, the standby no longer has to access the primary but starts by accessing its local definition. Taking this further: with a global temporary table (originally defined at the primary, of course), and with temp_undo_enabled
set to true (which is the default for ADG standbys), you can even execute DML like the following on GTTs:
insert into my_gtt1 select session_seq1.nextval, {other columns} from …
I have to admit that I haven’t set up a system to look for side effects and unexpected overheads in this approach – so do test it and examine the session events and session activity stats very carefully before taking it to production.
Another benefit of the session-private sequence is that since you are no longer competing for a public sequence cache, the latch contention due to heavy usage of a very popular sequence will be reduced. I was a little disappointed, however, when I tested the feature on 19.11.0.0: a global sequence needed 3 latch gets per call to nextval
while a session sequence still needed 2 latch gets – an improvement, but not as much of an improvement as I had assumed that I would see. Possibly the latch gets are currently needed to check that the sequence definition hasn’t changed since the last call.
A detail to remember when you define a session sequence is that some standard sequence options (cache/nocache
, order/noorder
) are irrelevant and will be ignored.
Restart
One of the FAQs about sequences used to be “how can I reset the sequence?” The answer was usually something along the lines of
- work out the difference between the current value and the
start with
value, - alter the
increment by
to the negative of that difference - select
nextval
from dual; - alter the
increment by
back to its original value
This method was a little risky since someone else might manage to slip in a select nextval
in the brief gap between steps 2 and 4, leaving you with a sequence that was nowhere near where you wanted it to be. In 12.1.0.2, one of the options in the alter sequence
command is to restart
.
Unfortunately, if you try this on a base-level 12.1.0.2, you will find that Oracle raises an error:
SQL> alter sequence s1 restart; alter sequence s1 restart * ERROR at line 1: ORA-00604: error occurred at recursive SQL level 1 ORA-01407: cannot update ("SYS"."SEQ$"."HIGHWATER") to NULL
The dictionary table for sequences (seq$
) has a column highwater
declared as NOT
NULL
, and it’s easy to see from a trace file that the internal code to restart a sequence doesn’t supply a value for this column in its call to update the seq$
table. This is probably bug 19602919, for which a patch is available on several platforms, but the bug is unpublished, so I haven’t been able to confirm the details.
There are a couple of little traps to the restart
option. First, if you restart a session sequence, you will find that every session that is currently using that sequence will immediately see the effects of the restart; the restart is not restricted to just your own session. I’d like to see the restart made completely private, but since it’s just one of many options in the alter sequence
command, I can understand why it might be difficult to make a special case for it. Of course, for similar reasons, you can’t restart a sequence in a read-only database – Oracle will raise the error you might expect: ORA-16000: database or pluggable database open for read-only access.
The second trap appears with incomplete statements of action. Here’s an example (run on 19.11.0.0):
drop sequence s1; create sequence s1 start with 100 increment by 2 cache 1000 minvalue 10 maxvalue 1e6 ; select s1.nextval from dual; select s1.nextval from dual; alter sequence s1 restart; select s1.nextval from dual; select s1.nextval from dual;
What values do you think the four calls for s1.nextval
will produce? You may be expecting 100, 102, 100, 102. However, it’s actually 100, 102, 10, 12. Oracle doesn’t remember the original start
with
clause; it restarts at your minvalue
unless you explicitly include a start with value in the call to restart. Perhaps you guessed correctly what would happen because I supplied a hint by explicitly setting the minvalue
to a non-default value If I hadn’t done that, would you have guessed that Oracle would restart at the default minvalue
of 1?
Identity
One of the questions that used to appear regularly on Oracle forums was: “How do I find out which sequence is being used to generate the key for this table?” The most typical answer was that there was no “tight” connection between sequences and primary keys – you create a sequence, you write code (often seen in a trigger) to select from the sequence and populate the column.
After many years Oracle allowed schema_name.sequence_name.nextval
to be used as a default value for a column, making the connection much tighter. This didn’t stop anyone from using the nominated sequence for other reasons, even to the extent of making it the default value for columns in several other tables.
Finally, in 12cR1, Oracle introduced the “identity” mechanism, which sets up a rigid link between a column and a system-generated sequence. There can be only one identity column in a table, and the sequence cannot be accessed in any way other than Oracle’s internal access to populate the column. The one identity per table rule is enforced by the simple implementation detail that the name of the sequence includes the object_id
of the table in the format: ISEQ$$_{table_object_id}
The identity column doesn’t conform 100% to expectations, however. It doesn’t automatically get declared unique, though it automatically acquires a not
null
declaration and constraint. If you want the column to be used as the primary key, you have to declare it as such.
There are three variants on adding an identity column to a table – shown below in a single statement with two of the variants commented out:
create table t (n1 number(6,0)); alter table t add ident number(12,0) generated always -- by default -- by default on null as identity cache 1000 start with 10 maxvalue 1e6 ;
The example creates a table with a single numeric (integer) column called n1
. It adds a column called ident
, which I’ve defined as a numeric (integer) column and declared as an identity column. Although I’m not allowed to name the sequence that will supply values for the column, I’ve shown that I’m allowed to supply standard sequence options that dictate how the values will be generated.
To explain how the three identity variants (always
, by
default
, by
default
on
null
) work, I can insert three carefully constructed rows into the three versions of the table and see what the results look like when I select them back. Here are the three insert statements:
insert into t(n1) values (1); insert into t (n1, ident) values (2,null); insert into t (n1, ident) values (3,101); select * from t;
In the first case, I haven’t inserted the ident column at all.
In the second case, I’ve inserted an explicit null into the ident column
In the last case, I’ve inserted a non-null value into the ident column
Here are the three sets of effects from the three separate tests (with a little cosmetic editing for ease of reading):
Generated always
1 row created. ORA-32795: cannot insert into a generated always identity column ORA-32795: cannot insert into a generated always identity column N1 IDENT ---------- ---------- 1 10
In this first case, two of the inserts fail because I’ve supplied an input (albeit that one of them is an explicit null) for the ident column. The first of the three inserts didn’t try to supply anything at all for ident and has used the sequence to generate the value 10.
Generated by default
1 row created. ORA-01400: cannot insert NULL into ("TEST_USER"."T"."IDENT") 1 row created. N1 IDENT ---------- ---------- 1 10 3 101
In the second case, Oracle used the sequence to generate the value 10 when I didn’t include ident in my insert statement, and it accepted the actual value I’d supplied for ident for the third statement. However, when I tried to insert a row where the ident column was supplied with an explicit null, Oracle rejected it. The column acquired a not null declaration as it became an identity, so I can’t insert a null, but Oracle is only allowed to use the default value if the insert hasn’t attempted to supply anything at all for the column.
Generated by default on null
1 row created. 1 row created. 1 row created. N1 IDENT ---------- ---------- 1 10 2 11 3 101
Finally, I have a version where all three inserts are acceptable. The insert which doesn’t supply an input for ident is supplied with a value by the sequence. The insert with the explicit null supplied for ident is overwritten by a value supplied by the sequence, and the insert with a non-null ident accepts that value.
There are other restrictions on the ident column that vary with the identity declaration. You can’t update the identity column if it is declared generated always
, but you can update it if it’s generated by default
or generated by default on null
, provided you don’t try to update it to null.
It’s nice to have a choice of strategies, and the best choice for your application may depend on the style of code that has already been written. You may also decide that you want to change an existing application that uses an older mechanism (such as a simple default
sequence.nextval
declaration) to take advantage of the new identity mechanism This might mean you have to convert your existing data using one mechanism, then go into production using another – e.g., conversion using by default on null
then changing to always
when the data is complete.
Add/Drop/Modify identity
Despite the heading, I’m going to start with dropping an identity because it’s the easy option:
alter table t modify ident drop identity;
That’s it – the sequence is dropped. The not
null
declaration and constraint disappear, but the column is still there. Big warning, though: if you do this, you can’t just put it back again. You can’t modify an existing column to become an identity; you can only add a new identity column.
This restriction seems like a bit of a show-stopper to me (and I may be wrong), but it looks as if you can’t take a table with a user-coded “identity-like” column and tell Oracle “I want this column to become an Oracle-managed identity.”
You’ve already seen the example of adding an identity to a table. However, in my original example, I created a table and added the identity column while it was still empty, and that hides a threat. If you want to add an identity column to a table with existing data, then Oracle will lock
the
table
before using the generated sequence to update every row. This may lead to some rows being migrated, of course, especially if you’ve used the scale
extend
option.
It is possible to modify the definition of an identity – mainly to allow you to correct errors in the way you’ve defined the underlying sequence: a larger cache size is the most likely requirement. Imagine, however, that you’ve used by
default
or by
default
on
null
in your identity definition. This would have allowed your application to insert values greater than the current value of the underlying sequence.
Now consider the possibility that you’ve created a primary key index on that column. What are you going to do when the inevitable collision arrives, and the supporting sequence reaches a value that has previously been manually entered? You’re going to get a “duplicate key” error and will need to adjust the sequence to bypass the value. Though it’s not documented in the earlier manuals, there’s a special case for start
with
when a sequence is associated with an identity column:
alter table t modify ident generated as identity start with limit value;
The special case of limit
value
tells Oracle to scan the table for the largest value in the column and jump the sequence to the next value above that.
Oracle 12c sequences
This final article on sequences has provided a brief tour of the scale, shared, keep, and session options for sequences. The scale option was covered in more detail in an earlier article, and the shard and keep options are likely to be used by only a small set of more sophisticated users. The session
option for creating “private” sequences that could be used in read-only databases is the one most likely to be of general use.
I’ve highlighted the appearance (after a very long wait) of the restart
option for sequences and have highlighted a couple of little traps with its use: where will a sequence restart from, and what side effects will a restart have for a session sequence that is currently in use in multiple sessions.
Finally, the article covered the identity column declaration and how it is dependent on a system-generated sequence then used a simple example to show the effects of the three different definitions on data input. Two of the definitions allow for data creation that could cause a primary key (or unique) constraint to raise an exception, so the article explained the special option for modifying the sequence to jump above the boundary value in the column. Also noted, that it’s easy to remove the identity mechanism from a column, but a lot more work to add the identity mechanism to an existing data set.
The post Oracle sequences – 12c features including “identity” appeared first on Simple Talk.
from Simple Talk https://ift.tt/3mi9Vms
via
No comments:
Post a Comment