N 函数会导致现有查询出现问题吗?
Can N function cause problems with existing queries?
我们使用 Oracle 10g
和 Oracle 11g
。
我们还有一个层可以自动编写查询,来自用 .net 编写的伪SQL 代码(类似于 Python 的 SqlAlchemy)。
我们的图层目前用单引号 '
包裹任何字符串,如果包含非 ANSI 字符,它会自动将 UNISTR
与写为 unicode 字节的特殊字符(如 [=14= ]E0
).
现在我们创建了一个使用以下结构进行多次插入的方法:
INSERT INTO ... (...)
SELECT ... FROM DUAL
UNION ALL SELECT ... FROM DUAL
...
此算法可以组成查询,其中相同的字符串字段有时作为 'my simple string'
传递,有时包装为 UNISTR('my string with special chars like [=17=]E0')
。
所描述的情况导致ORA-12704: character set mismatch
。
一种解决方案是使用 INSERT ALL
结构,但与现在使用的结构相比,它 非常慢。
另一个解决方案是指示我们的层将 N
放在任何字符串的前面(除了已经用 UNISTR
包裹的字符串)。这很简单。
我只想知道这是否会对现有查询造成任何副作用。
注意:我们在 DB 上的所有字段都是 NCHAR
或 NVARCHAR2
。
Oracle 参考:http://docs.oracle.com/cd/B19306_01/server.102/b14225/ch7progrunicode.htm
基本上您要问的是,使用或不使用 N 函数存储字符串的方式是否有区别。
你可以自己检查考虑:
SQL> create table test (val nvarchar2(20));
Table TEST created.
SQL> insert into test select n'test' from dual;
1 row inserted.
SQL> insert into test select 'test' from dual;
1 row inserted.
SQL> select dump(val) from test;
DUMP(VAL)
--------------------------------------------------------------------------------
Typ=1 Len=8: 0,116,0,101,0,115,0,116
Typ=1 Len=8: 0,116,0,101,0,115,0,116
如您所见,完全相同,因此没有副作用。
之所以如此完美,是因为 unicode 的优雅
如果您有兴趣,这里有一个很好的解释视频
我假设你得到了一个错误 "ORA-12704: character set mismatch"
因为你的引号内的数据被认为是 char 但你的字段是 nchar 所以 char 使用不同的字符集整理,一个使用 NLS_CHARACTERSET
,另一个 NLS_NCHAR_CHARACTERSET
.
当您使用 UNISTR
函数时,它会将数据从 char
转换为 nchar
(无论如何也会将编码值转换为字符),就像 Oracle docs 说:
"UNISTR takes as its argument a text literal or an expression that
resolves to character data and returns it in the national character
set."
当您使用 N
或 TO_NCHAR
显式转换值时,您只会获得 NLS_NCHAR_CHARACTERSET
中的值而无需解码。如果您有一些像这样编码的值 "[=20=]E0"
,它们将不会被解码并被视为未更改。
因此,如果您有如下插入内容:
insert into select N'my string with special chars like [=10=]E0',
UNISTR('my string with special chars like [=10=]E0') from dual ....
您在第一个插入字段中的数据将是:'my string with special chars like [=21=]E0'
而不是 'my string with special chars like à'
。这是我知道的唯一副作用。其他查询应该已经使用 NLS_NCHAR_CHARACTERSET 编码,因此使用显式转换应该没有问题。
顺便说一下,为什么不将所有值都插入为 N'my string with special chars like à'
?如果您在 'upper level' 软件中使用不同的编码,只需先将它们编码为 UTF-16(我假设您对 nchars 使用 UTF-16)。
- n 函数的使用 - 您在上面已经有了答案。
如果您有机会更改数据库的字符集,那确实会让您的生活更轻松。我在做庞大的生产系统,发现由于存储 space 便宜的趋势,干脆大家都转向 AL32UTF8,国际化的麻烦慢慢成为过去的痛苦回忆。
我发现最简单的方法是使用 AL32UTF8 作为数据库实例的字符集,并在所有地方简单地使用 varchar2。我们正在通过 JDBC 作为绑定变量读写标准 Java unicode 字符串,并且 fiddle.
您构建 SQL 插入的巨大文本的想法可能由于多种原因无法很好地扩展:
- 允许的最大语句长度是固定的 SQL - 因此它不适用于 10000 次插入
- 建议使用绑定变量(这样你也不会把 n'xxx' 和 unistr 搞得一团糟)
- 动态创建新的 SQL 语句的想法是非常有用的资源。它不允许 Oracle 缓存任何执行计划,并且会让 Oracle 在每次调用时硬解析你的 looong 语句。
您要实现的是批量插入。使用 Oracle 驱动程序的 JDBC 批处理模式以光速执行,参见例如:http://viralpatel.net/blogs/batch-insert-in-java-jdbc/
请注意,插入速度还受触发器(必须执行)和外键约束(必须验证)的影响。因此,如果您要插入超过几千行,请考虑禁用触发器和外键约束,并在插入后启用它们。 (您将丢失触发器调用,但插入后的约束验证会产生影响。)
还要考虑回滚段大小。如果你要插入一百万条记录,那将需要一个巨大的回滚段,这可能会导致存储介质上的严重交换。在每 1000 条记录之后提交是一个很好的经验法则。
(Oracle 使用版本控制而不是共享锁,因此具有未提交更改的 table 始终可用于读取。1000 条记录的提交率意味着大约每秒 1 次提交 - 慢到足以利用写入缓冲区,但速度足够快,不会干扰其他愿意更新相同内容的人 table。)
我们使用 Oracle 10g
和 Oracle 11g
。
我们还有一个层可以自动编写查询,来自用 .net 编写的伪SQL 代码(类似于 Python 的 SqlAlchemy)。
我们的图层目前用单引号 '
包裹任何字符串,如果包含非 ANSI 字符,它会自动将 UNISTR
与写为 unicode 字节的特殊字符(如 [=14= ]E0
).
现在我们创建了一个使用以下结构进行多次插入的方法:
INSERT INTO ... (...)
SELECT ... FROM DUAL
UNION ALL SELECT ... FROM DUAL
...
此算法可以组成查询,其中相同的字符串字段有时作为 'my simple string'
传递,有时包装为 UNISTR('my string with special chars like [=17=]E0')
。
所描述的情况导致ORA-12704: character set mismatch
。
一种解决方案是使用 INSERT ALL
结构,但与现在使用的结构相比,它 非常慢。
另一个解决方案是指示我们的层将 N
放在任何字符串的前面(除了已经用 UNISTR
包裹的字符串)。这很简单。
我只想知道这是否会对现有查询造成任何副作用。
注意:我们在 DB 上的所有字段都是 NCHAR
或 NVARCHAR2
。
Oracle 参考:http://docs.oracle.com/cd/B19306_01/server.102/b14225/ch7progrunicode.htm
基本上您要问的是,使用或不使用 N 函数存储字符串的方式是否有区别。
你可以自己检查考虑:
SQL> create table test (val nvarchar2(20));
Table TEST created.
SQL> insert into test select n'test' from dual;
1 row inserted.
SQL> insert into test select 'test' from dual;
1 row inserted.
SQL> select dump(val) from test;
DUMP(VAL)
--------------------------------------------------------------------------------
Typ=1 Len=8: 0,116,0,101,0,115,0,116
Typ=1 Len=8: 0,116,0,101,0,115,0,116
如您所见,完全相同,因此没有副作用。
之所以如此完美,是因为 unicode 的优雅
如果您有兴趣,这里有一个很好的解释视频
我假设你得到了一个错误 "ORA-12704: character set mismatch"
因为你的引号内的数据被认为是 char 但你的字段是 nchar 所以 char 使用不同的字符集整理,一个使用 NLS_CHARACTERSET
,另一个 NLS_NCHAR_CHARACTERSET
.
当您使用 UNISTR
函数时,它会将数据从 char
转换为 nchar
(无论如何也会将编码值转换为字符),就像 Oracle docs 说:
"UNISTR takes as its argument a text literal or an expression that resolves to character data and returns it in the national character set."
当您使用 N
或 TO_NCHAR
显式转换值时,您只会获得 NLS_NCHAR_CHARACTERSET
中的值而无需解码。如果您有一些像这样编码的值 "[=20=]E0"
,它们将不会被解码并被视为未更改。
因此,如果您有如下插入内容:
insert into select N'my string with special chars like [=10=]E0',
UNISTR('my string with special chars like [=10=]E0') from dual ....
您在第一个插入字段中的数据将是:'my string with special chars like [=21=]E0'
而不是 'my string with special chars like à'
。这是我知道的唯一副作用。其他查询应该已经使用 NLS_NCHAR_CHARACTERSET 编码,因此使用显式转换应该没有问题。
顺便说一下,为什么不将所有值都插入为 N'my string with special chars like à'
?如果您在 'upper level' 软件中使用不同的编码,只需先将它们编码为 UTF-16(我假设您对 nchars 使用 UTF-16)。
- n 函数的使用 - 您在上面已经有了答案。
如果您有机会更改数据库的字符集,那确实会让您的生活更轻松。我在做庞大的生产系统,发现由于存储 space 便宜的趋势,干脆大家都转向 AL32UTF8,国际化的麻烦慢慢成为过去的痛苦回忆。
我发现最简单的方法是使用 AL32UTF8 作为数据库实例的字符集,并在所有地方简单地使用 varchar2。我们正在通过 JDBC 作为绑定变量读写标准 Java unicode 字符串,并且 fiddle.
您构建 SQL 插入的巨大文本的想法可能由于多种原因无法很好地扩展:
- 允许的最大语句长度是固定的 SQL - 因此它不适用于 10000 次插入
- 建议使用绑定变量(这样你也不会把 n'xxx' 和 unistr 搞得一团糟)
- 动态创建新的 SQL 语句的想法是非常有用的资源。它不允许 Oracle 缓存任何执行计划,并且会让 Oracle 在每次调用时硬解析你的 looong 语句。
您要实现的是批量插入。使用 Oracle 驱动程序的 JDBC 批处理模式以光速执行,参见例如:http://viralpatel.net/blogs/batch-insert-in-java-jdbc/
请注意,插入速度还受触发器(必须执行)和外键约束(必须验证)的影响。因此,如果您要插入超过几千行,请考虑禁用触发器和外键约束,并在插入后启用它们。 (您将丢失触发器调用,但插入后的约束验证会产生影响。)
还要考虑回滚段大小。如果你要插入一百万条记录,那将需要一个巨大的回滚段,这可能会导致存储介质上的严重交换。在每 1000 条记录之后提交是一个很好的经验法则。
(Oracle 使用版本控制而不是共享锁,因此具有未提交更改的 table 始终可用于读取。1000 条记录的提交率意味着大约每秒 1 次提交 - 慢到足以利用写入缓冲区,但速度足够快,不会干扰其他愿意更新相同内容的人 table。)