Quassnoi's points regarding are valid but it's worth remembering that not all CHECK
constraints are equal. In the following tests I benchmarked the REGEXP_LIKE()
check against two more "old-fashioned" checks; the first converts the value into a string of zeroes and then does an equality check, and the second does a range check using BETWEEN()
.
"Wall clock" tests are sensitive to ambient conditions (such as a checkpoint firing) so we need to run them several times. The other thing to bear in mind is that performance can vary from version to version. For instance, I'm running on 11g and the regex check ran consistently at 9-10 seconds, which suggests Oracle have optimized it quite considerably since 10g. On the other hand the unchecked inserts ran at 1.7 - 2-ish seconds, so regex is still relatively expensive. The other checks weighed in at about 2.5 - 3.0 seconds, which is roughly a 50% hit for integrity.
Whether it is worth paying that toll really depends on how you feel about your data. Experience says relying on the client to enforce the data rules inevitably means that at some point the rules will be broken. Either because a developer omits to apply them or deletes them from the application. Or because somebody attaches to the database a fresh client application (e.g. batch upload, web service) which does not include those rules.
Finally, most applications are not going to be loading one million rows at a time. Compared to the network round trip, the microseconds required to apply checks to a single insert or upload are probably a trivial overhead.
SQL> CREATE TABLE t_check (value VARCHAR2(50))
2 /
Table created.
Elapsed: 00:00:00.01
SQL>
SQL> INSERT
2 INTO t_check
3 SELECT level
4 FROM dual
5 CONNECT BY
6 level <= 1000000
7 /
1000000 rows created.
Elapsed: 00:00:01.68
SQL>
SQL> prompt Regex check
Regex check
SQL>
SQL> drop table t_check
2 /
Table dropped.
Elapsed: 00:00:00.37
SQL> CREATE TABLE t_check (value VARCHAR2(50) NOT NULL
2 , CHECK(REGEXP_LIKE(value, '^[0-9]{1,10}$')))
3 /
Table created.
Elapsed: 00:00:00.07
SQL>
SQL> INSERT
2 INTO t_check
3 SELECT level
4 FROM dual
5 CONNECT BY
6 level <= 1000000
7 /
1000000 rows created.
Elapsed: 00:00:09.53
SQL>
SQL> prompt old fashioned "mask" check
old fashioned "mask" check
SQL>
SQL> drop table t_check
2 /
Table dropped.
Elapsed: 00:00:00.59
SQL> CREATE TABLE t_check
2 (value VARCHAR2(50) NOT NULL
3 , CHECK(translate(lpad(value, 20, '0')
4 , '1234567890', '0000000000') = '00000000000000000000' ))
5 /
Table created.
Elapsed: 00:00:00.01
SQL>
SQL> INSERT
2 INTO t_check
3 SELECT level
4 FROM dual
5 CONNECT BY
6 level <= 1000000
7 /
1000000 rows created.
Elapsed: 00:00:02.82
SQL>
SQL> prompt old fashioned "range" check
old fashioned "range" check
SQL>
SQL> drop table t_check
2 /
Table dropped.
Elapsed: 00:00:00.39
SQL> CREATE TABLE t_check
2 (value VARCHAR2(50) NOT NULL
3 , CHECK( value between 1 and 1000000))
4 /
Table created.
Elapsed: 00:00:00.01
SQL>
SQL> INSERT
2 INTO t_check
3 SELECT level
4 FROM dual
5 CONNECT BY
6 level <= 1000000
7 /
1000000 rows created.
Elapsed: 00:00:02.23
SQL>