【问题标题】:Can N function cause problems with existing queries?N 函数会导致现有查询出现问题吗?
【发布时间】:2016-06-16 21:26:29
【问题描述】:

我们使用Oracle 10gOracle 11g

我们还有一个层来自动组合查询,来自用 .net 编写的伪 SQL 代码(类似于 Python 的 SqlAlchemy)。

我们的层目前将任何字符串用单引号 ' 包裹起来,如果包含非 ANSI 字符,它会自动将 UNISTR 与写成 unicode 字节的特殊字符(如 \00E0)组合起来。

现在我们使用以下构造创建了一个执行多次插入的方法:
INSERT INTO ... (...) SELECT ... FROM DUAL UNION ALL SELECT ... FROM DUAL ...

该算法可以组合查询,其中相同的字符串字段有时被传递为'my simple string',有时被包装为UNISTR('my string with special chars like \00E0')

所述条件导致ORA-12704: character set mismatch

一种解决方案是使用INSERT ALL 构造,但与现在使用的相比,它非常慢

另一个解决方案是指示我们的层将N 放在任何字符串的前面(除了那些已经用UNISTR 包裹的字符串)。这很简单。

我只是想知道这是否会对现有查询造成任何副作用。

注意:我们在 DB 上的所有字段都是 NCHARNVARCHAR2


甲骨文参考:http://docs.oracle.com/cd/B19306_01/server.102/b14225/ch7progrunicode.htm

【问题讨论】:

  • 如果您知道目标列的大小,也可以进行强制转换。或者您的图层可能支持适当的批量插入机制。但是肯定使用n'...' 只是避免了在插入过程中文字的隐式转换,从您的数据库字符集到国家字符集?
  • @AlexPoole 真诚的,我不明白你的问题......
  • 每条语句插入多少行?如果INSERT ALLUNION ALL 慢,您可能会遇到Oracle 解析问题,正如我在回答here 中所解释的那样。将INSERT ALL 分成更小的块可能就足以避免大型 SQL 语句的长时间解析。
  • @JonHeller 目前,我将 row-per-query 常量设置为 100。有了这个值,我可以在 ~21 秒内插入 81.000 行(x 23 列)。

标签: oracle unicode bulkinsert


【解决方案1】:

基本上你要问的是,使用或不使用 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 的优雅

如果你有兴趣,这里有一个很好的视频来解释它

https://www.youtube.com/watch?v=MijmeoH9LT4

【讨论】:

  • 在字符串文字的任何地方应用 N 是否会降低性能?
  • “在字符串文字的任何地方都应用 N 会降低性能吗?”不,它不能,因为插入到 nchar 列中的任何 char 值都隐式或显式转换为 nchar。
  • @MikhailovValentine 谢谢。那么,对于N,我只是在显式处理一个无论如何都会隐式发生的过程?
【解决方案2】:

我假设您收到错误 "ORA-12704: character set mismatch",因为引号内的数据被视为 char,但您的字段是 nchar,因此使用不同的字符集对 char 进行整理,一个使用 NLS_CHARACTERSET,另一个使用 NLS_NCHAR_CHARACTERSET

当您使用UNISTR 函数时,它将数据从char 转换为nchar(在任何情况下也将编码值转换为字符),正如Oracle docs 所说:

"UNISTR 将文本文字或表达式作为其参数 解析为字符数据并以国家字符返回 设置。”

当您使用NTO_NCHAR 显式转换值时,您只能在NLS_NCHAR_CHARACTERSET 中获得值而无需解码。如果您有一些像这样"\00E0" 编码的值,它们将不会被解码并被视为未更改。

所以如果你有一个插入,例如:

   insert into  select N'my string with special chars like \00E0', 
    UNISTR('my string with special chars like \00E0') from dual ....

您在第一个插入字段中的数据将是:'my string with special chars like \00E0' 而不是 'my string with special chars like à'。这是我知道的唯一副作用。其他查询应该已经使用 NLS_NCHAR_CHARACTERSET 编码,所以使用显式转换应该没有任何问题。

顺便说一句,为什么不将所有值插入为N'my string with special chars like à'?如果您在“上层”软件中使用不同的编码,请先将它们编码为 UTF-16(我假设您将 UTF-16 用于 nchars)。

【讨论】:

  • “我假设您收到错误“ORA-12704:字符集不匹配”,因为引号内的数据被视为 char,但您的字段是 nchar” 不,我明白了错误是因为我将非 unicode 和 unicode 文本与UNION ALL 混合在一起。
  • “但是如果你有一些像“\00E0”这样编码的值,它们将不会被解码,将被视为原样。” 包含特殊字符的字符串会自动用UNISTR是我们层的,其他的不是。这就是混合出现的原因,这就是为什么我需要 N 用于其他字符串。
  • "顺便说一句,为什么不把所有值都插入N'my string with special chars like à'" 所以你说使用UNISTR('\00E0')和@987654340没有区别@?
  • " 使用 UNISTR('\00E0') 和 N'à' 没有区别" 至少当我使用 JDBC 或 SQL Developer 插入时,我找不到区别。
  • “不,我收到错误,因为我将非 unicode 和 unicode 文本与 UNION ALL 混合在一起。”尝试使用 char 和 nchar 列创建一个测试表,并尝试使用 N'string' 插入 n char 和 'string' 插入 char - 它会起作用。
【解决方案3】:
  • 使用 n 函数 - 上面已经有了答案。

如果您有机会更改数据库的字符集,那真的会让您的生活更轻松。我在做庞大的生产系统,发现由于存储空间便宜,所有人都转向 AL32UTF8 的趋势,国际化的麻烦慢慢成为过去的痛苦回忆。

我发现最简单的方法是使用 AL32UTF8 作为数据库实例的字符集,并且在任何地方都使用 varchar2。我们通过 JDBC 读取和写入标准 Java unicode 字符串作为绑定变量,不会造成任何伤害,并且会摆弄。

由于多种原因,您构建大量 SQL 插入文本的想法可能无法很好地扩展:

  • 最大允许 SQL 语句的长度是固定的 - 因此它不适用于 10000 次插入
  • 建议使用绑定变量(然后你也没有 n'xxx' vs unistr 混乱)
  • 动态创建新的SQL 语句的想法是非常节省资源的。它不允许 Oracle 缓存任何执行计划,并且会在每次调用时使 Oracle 硬解析您的 looong 语句。

您想要实现的是批量插入。使用 Oracle 驱动程序的 JDBC 批处理模式以光速执行,例如:http://viralpatel.net/blogs/batch-insert-in-java-jdbc/

请注意,插入速度还受触发器(必须执行)和外键约束(必须验证)的影响。因此,如果您要插入超过数千行,请考虑禁用触发器和外键约束,并在插入后启用它们。 (您将丢失触发器调用,但插入后的约束验证会产生影响。)

还要考虑回滚段的大小。如果您要插入一百万条记录,那将需要一个巨大的回滚段,这可能会导致存储介质上的严重交换。在每 1000 条记录之后提交是一个很好的经验法则。

(Oracle 使用版本控制而不是共享锁,因此具有未提交更改的表始终可用于读取。1000 条记录的提交率意味着每秒大约 1 次提交 - 足够慢以受益于写入缓冲区,但足够快以至于不会干扰与其他愿意更新同一张表的人。)

【讨论】:

  • “最大允许 SQL 语句的长度是固定的 - 所以它不适用于 10000 次插入”,根本不正确。 Oracle没有固定长度限制,请参阅stackoverflow.com/questions/14355819/…。顺便说一句,我们的层会自动将查询拆分为预定义的大小,所以我们不需要担心这些事情。
  • “你要实现的是批量插入。使用Oracle驱动的JDBC批处理模式” 我知道批量插入有很多方法,即从一个格式化的文本文件,但事实并非如此。我们的层还为 SqlServer 和 Postgres 编写查询。顺便说一句,没有人提到 Java,我们使用 .net。
  • “考虑禁用触发器和外键约束” 我们的配置中没有触发器。无论如何,请注意,触发器通常是您离不开的,尤其是在它们进行数据修改时。
  • "考虑禁用触发器和外键约束" 外键约束可能对数据插入率有影响,但是如果禁用了,需要稍后重新启用...并且重新启用(和检查)的时间与插入期间节省的时间相当
  • “还要考虑回滚段的大小。” 没有人提到事务。无论如何,它们是可以使用的。在某些应用程序中,提交 @ 1000 条记录阈值是可笑的。
猜你喜欢
  • 1970-01-01
  • 2014-12-26
  • 1970-01-01
  • 1970-01-01
  • 2012-08-21
  • 1970-01-01
  • 2011-09-18
  • 2023-04-04
相关资源
最近更新 更多