一直以来,很多用过ORACLE数据库的开发人员,都知道在ORACLE中,字符类型可以为varchar2,也可以为nvarchar2,但是很多人都不知道这两种类型有什么区别,同样还有char/nchar,clob/nclob这些,所以今天来谈谈我对这些数据类型的理解。
老的oracle开发人员中,可能流传这这样一句传言,
“如果要省存储空间,建表时,字段内容里如果中文占了大多数,就用nvarchar2类型;如果内容是英文和数字为主的字符串,就用varchar2类型。”
首先说明一下,这句话在绝大多数情况下,的确是对的。
但是,这其实,是在特定的条件下,仅用例证得到的结论,后面会说明原因。
我们先做个实验,看看为什么会有上面这个传言
假设数据库字符集为AL32UTF8,建个表,两个字段,分别为varchar2和nvarchar2,插入数字、英文字母、汉字
CREATE TABLE TEST_CHARSET (A VARCHAR2(100), B NVARCHAR2(100));
INSERT INTO TEST_CHARSET VALUES (12,n'12');
INSERT INTO TEST_CHARSET VALUES ('ab',n'ab');
INSERT INTO TEST_CHARSET VALUES ('啊',n'啊');
然后使用lengthb/dump等函数查看字节长度
select dump(a,1016),dump(b,1016),t.* from TEST_CHARSET t;

看上去,的确对于汉字,nvarchar2的长度比varchar2要短,看上去不论啥字符插入到nvarchar2中都是2个字节,对于数字和字母就太占空间了。
但是,nvarchar2的字符集显示的是AL16UTF16,而非varchar2对应字段中的AL32UTF8
所以,我们得回头看看,nvarchar2这个数据类型到底是什么
https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/Data-Types.html#GUID-DF7E10FC-A461-4325-A295-3FD4D150809E
官方文档说
数据类型指定国家字符集中的NVARCHAR2变长字符串。创建数据库时,将国家字符集指定为 AL16UTF16 或 UTF8。AL16UTF16 和 UTF8 是 Unicode 字符集的两种编码形式(对应的 UTF-16 和 CESU-8),因此NVARCHAR2是仅限 Unicode 的数据类型。

在21c数据库创建界面中可以看到,数据库默认字符集编码为AL32UTF8,国家字符集编码默认为AL16UTF16,可选UTF8。
文档说
一个代码点在 AL16UTF16 中始终具有 2 个字节,在 UTF8 中始终具有 1 到 3 个字节,具体取决于代码点编码的特定字符。
文档此处存在歧义,AL16UTF16也会有4个字节的情况,比如
SELECT UTL_I18N.raw_to_Nchar('D840DC43'),
DUMP(UTL_I18N.raw_to_Nchar('D840DC43'),1016) FROM DUAL;

Oracle Database 21c 支持 Unicode 版本 12.1
我特意去翻了unicode12.1版本的文档,发现补充码的部分对应不上,所以ORACLE这句话其实是有前提条件的
https://www.unicode.org/Public/12.1.0/charts/CodeCharts.pdf
oracle的UTF-8为1到3个字节,而unicode标准中的UTF-8是1~4个字节,所以oracle的UTF-8也不符合unicode标准,甚至还会把unicode中FFFF后面的字符,转换成6个字节来进行存储,比如unicode编码20043(𠁃),对应的UTF-16的D840DC43,存成UTF-8则为eda180edb183,但按照UNICODE标准,它的UTF-8编码应该为F0A08183

其实默认情况下,oracle中的UTF-8根本就不是UTF-8,而是CESU-8,这个在ORACLE官方文档中有提到
https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/Data-Types.html#GUID-CC15FC97-BE94-4FA4-994A-6DDF7F1A9904
AL16UTF16 和 UTF8 是 Unicode 字符集的两种编码形式(对应的 UTF-16 和 CESU-8)
而且ORACLE官方文档还这么说了
https://docs.oracle.com/en/database/oracle/oracle-database/21/nlspg/supporting-multilingual-databases-with-unicode.html#GUID-F3B0B4F7-B6D9-473D-840F-F98998F37981
CESU-8 不是核心 Unicode 标准的一部分。Unicode 联盟发布的 Unicode 技术报告 #26 对此进行了描述。CESU-8 是一种与 UTF-8 相同的兼容性编码形式,除了它的补充字符表示。在 CESU-8 中,补充字符被表示为代理对,就像在 UTF-16 中一样。要获得补充字符的 CESU-8 编码,首先将字符编码为 UTF-16,然后将每个代理代码单元视为具有相同值的代码点。然后,将 UTF-8 编码规则(位转换)应用于每个代码点。这将产生两个三字节表示,总共六个字节。
CESU-8 只有两个好处:
它具有与 UTF-16 相同的二进制排序顺序。
每个字符使用相同数量的代码(一个或两个)。这对于字符串处理中的字符长度语义很重要。
一般来说,应尽可能避免使用 CESU-8 编码形式。
关于CESU-8,可以参考下面这个对比

(此表来自https://infogalactic.com/info/CESU-8)
在unicode官方文档中也有一些介绍
https://www.unicode.org/reports/tr26/tr26-2.html
oracle文档中虽然体现出UTF-8和AL32UTF8不是同一种编码
| Character Set | Supported in RDBMS Release | Unicode Encoding Form | Unicode Version | Database Character Set | National Character Set |
|---|---|---|---|---|---|
| AL24UTFFSS | 7.2 to 8i | UTF-8 | 1.1 | Yes | No |
| UTF8 | 8.0 to 21c | CESU-8 | Oracle Database release 8.0 through Oracle8i Release 8.1.6: 2.1 | Yes | Yes |
| Oracle8i Database release 8.1.7 and later: 3.0 | (Oracle9i Database and later versions only) | ||||
| UTFE | 8.0 to 21c | UTF-EBCDIC | Oracle8i Database releases 8.0 through 8.1.6: 2.1 | YesFoot 1 | No |
| For Oracle8i Database release 8.1.7 and later: 3.0 | |||||
| AL32UTF8 | 9i to 21c | UTF-8 | Oracle9i Database release 1: 3.0 | Yes | No |
| Oracle9i Database release 2: 3.1 | |||||
| Oracle Database 10g, release 1: 3.2 | |||||
| Oracle Database 10g, release 2: 4.0 | |||||
| Oracle Database 11g: 5.0 | |||||
| Oracle Database 12c, release 1: 6.2 | |||||
| Oracle Database 12c, release 2: 7.0 | |||||
| Oracle Database 18c to Oracle Database 19c: 9.0 | |||||
| Oracle Database 21c: 12.1 | |||||
| AL16UTF16 | 9i to 21c | UTF-16 | Oracle9i Database release 1: 3.0 | No | Yes |
| Oracle9i Database release 2: 3.1 | |||||
| Oracle Database 10g, release 1: 3.2 | |||||
| Oracle Database 10g, release 2: 4.0 | |||||
| Oracle Database 11g: 5.0 | |||||
| Oracle Database 12c, release 1: 6.2 | |||||
| Oracle Database 12c, release 2: 7.0 | |||||
| Oracle Database 18c to Oracle Database 19c: 9.0 | |||||
| Oracle Database 21c: 12.1 |
https://docs.oracle.com/en/database/oracle/oracle-database/21/nlspg/supporting-multilingual-databases-with-unicode.html#GUID-CD422E4F-C5C6-4E22-B95F-CA9CABBCB543
但是,测试会发现,oracle中的AL32UTF8其实还是CESU-8的做法,
CREATE TABLE TEST_CHARSET (A VARCHAR2(100), B NVARCHAR2(100));
INSERT INTO TEST_CHARSET VALUES ('𠁃',n'𠁃');
select T.*,DUMP(A),dump(b) from TEST_CHARSET T;

而且可能还会导致严重的字符歧义,比如
SELECT UTL_I18N.raw_to_Nchar('D840DC43'),
TO_CHAR(UTL_I18N.raw_to_Nchar('D840DC43')) FROM DUAL;

再来看下面的例子,会发现第一列此时又符合UNICODE标准的UTF-8编码了。
也就是说,在ORACLE中,字段类型必须是国家字符集的字符数据类型才会符合UNICODE标准
(注意第四个值的二进制数据其实是对的,符合UTF8的4个字节,但是在非国家字符集的字符类型中无法正确解析)。
select
UTL_I18N.raw_to_Nchar(data => 'F0A08183', src_charset =>'AL32UTF8' ) N_UTF8,
UTL_I18N.raw_to_Nchar(data => 'D840DC43', src_charset =>'AL16UTF16' ) N_UTF16,
UTL_I18N.raw_to_char(data => 'eda180edb183', src_charset =>'AL32UTF8' ) UTF8,
UTL_I18N.raw_to_char(data => 'D840DC43', src_charset =>'AL16UTF16' ) UTF16,
DUMP( UTL_I18N.raw_to_char(data => 'D840DC43', src_charset =>'AL16UTF16' ) ,1016) UTF16_BINARY
FROM DUAL;

所以并不能说ORACLE不遵守UNICODE标准,而是它在兼容原有的CESU-8的基础上,另外提供了一种符合UNICODE标准的方式,即使用国家字符集的字符数据类型来进行数据存储。
也就是说,以上的函数,其实都隐含了两个不确定的参数,即数据库字符集和国家字符集。而这两个字符集,在建库的时候可以指定。也就是说,如果建库的时候不是选择的默认值,那么这些函数的查询结果就可能会有不同。
经过以上实验分析及相关资料查询后,我们可以做个这样的实验:
创建一个新库,把数据库字符集设置成ZHS16GBK,把国家字符集设置成UTF-8,
那么存入一个常见汉字到varchar2中会占2个字节,存入到nvarchar2中则占3个字节;存入一个英文字母到varchar2中和nvarchar2中均占1个字节,
传言被推翻。
当然,绝大多数情况下,不会有谁把国家字符集选择成UTF-8,甚至连ORACLE官方文档都是强烈建议,国家字符集要选AL16UTF16,所以本文前面说的这个传言依旧具有一定的指导意义。
| 国家字符集 | NCHAR 数据类型的最大列大小 | NVARCHAR2 数据类型的最大列大小(当 MAX_STRING_SIZE = STANDARD 时) | NVARCHAR2 数据类型的最大列大小(当 MAX_STRING_SIZE = EXTENDED 时) |
|---|---|---|---|
| AL16UTF16 | 1000 个字符 | 2000 个字符 | 16383 个字符 |
| UTF8 | 2000 个字符 | 4000 个字符 | 32767 个字符 |
(注:MAX_STRING_SIZE参数是在oracle12c版本中新增的
https://docs.oracle.com/en/database/oracle/oracle-database/21/refrn/MAX_STRING_SIZE.html#GUID-D424D23B-0933-425F-BC69-9C0E6724693C
)
一直以来,网上各种教程里都是说,lengthb查的是字节数,length查的是字符数。但ORACLE里的字符数,计算标准其实也有很多种,像下面的这个一个汉字,用length函数查它竟然是2个字符!因为length不是按照unicode标准来进行统计的,lengthc才是,另外还有针对UCS2/UCS4标准的长度计算函数
select
length('𠁃') n,
unistr('\D840\DC43') str,
dump(unistr('\D840\DC43'),1016) dp,
lengthb(unistr('\D840\DC43')) lenb,
length(unistr('\D840\DC43')) len,
lengthc(unistr('\D840\DC43'))lenc,
length2(unistr('\D840\DC43')) len2,
length4(unistr('\D840\DC43')) len4
from dual

所以,如果ORACLE 11g中的国家字符集为AL16UTF16,对于nvarchar2(2000)字段,是有可能存入1000个字就满了的(全部存UTF-16编码为4个字节的补充字符的情况下)
假设数据库字符集为ZHS16GBK,某次需要插入一个在这个字符集中不存在的汉字或其他语言的字符,该如何处理?
数据库的字符集建库时就确定了,不能轻易修改,如果把字段类型设置成国家字符集的数据类型,比如nvarchar2,那么就可以把这个ZHS16GBK中不存在的字符存进去了。(如果是子集改成母集,且不用对数据进行编码转换,那么可以使用DMU进行快速字符集修改)

某公司正在使用的一个纯英文的软件,对应的数据库字符集为WE8MSWIN1252,该软件把所有UI菜单及选项的描述都存在了数据库中,当该软件需要扩展多国语言时,只需要把描述列改成NVARCHAR2,然后增加一个语言ID,相关查询接口增加语言ID的逻辑,客户端代码采用UTF8,即可在不用重建数据库的情况下,扩展出UI多语言支持。
ORACLE以标准名称命名非标准的东西,而且时而标准时而不标准,的确很让人犯迷糊,我根据本文总结几点
select UTL_I18N.raw_to_Nchar(data => 'F0A08183', src_charset =>'AL32UTF8' ) from dual;
- 本文作者: DarkAthena
- 本文链接: https://www.darkathena.top/archives/about-nvarchar2-and-national-charset
- 版权声明: 本博客所有文章除特别声明外,均采用CC BY-NC-SA 3.0 许可协议。转载请注明出处!
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳
我正在尝试使用Curbgem执行以下POST以解析云curl-XPOST\-H"X-Parse-Application-Id:PARSE_APP_ID"\-H"X-Parse-REST-API-Key:PARSE_API_KEY"\-H"Content-Type:image/jpeg"\--data-binary'@myPicture.jpg'\https://api.parse.com/1/files/pic.jpg用这个:curl=Curl::Easy.new("https://api.parse.com/1/files/lion.jpg")curl.multipart_form_
无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD
本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01 客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02 数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit
文章目录一、概述简介原理模块二、配置Mysql使用版本环境要求1.操作系统2.mysql要求三、配置canal-server离线下载在线下载上传解压修改配置单机配置集群配置分库分表配置1.修改全局配置2.实例配置垂直分库水平分库3.修改group-instance.xml4.启动监听四、配置canal-adapter1修改启动配置2配置映射文件3启动ES数据同步查询所有订阅同步数据同步开关启动4.验证五、配置canal-admin一、概述简介canal是Alibaba旗下的一款开源项目,Java开发。基于数据库增量日志解析,提供增量数据订阅&消费。Git地址:https://github.co
我正在尝试在Rails上安装ruby,到目前为止一切都已安装,但是当我尝试使用rakedb:create创建数据库时,我收到一个奇怪的错误:dyld:lazysymbolbindingfailed:Symbolnotfound:_mysql_get_client_infoReferencedfrom:/Library/Ruby/Gems/1.8/gems/mysql2-0.3.11/lib/mysql2/mysql2.bundleExpectedin:flatnamespacedyld:Symbolnotfound:_mysql_get_client_infoReferencedf
文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,
SPI接收数据左移一位问题目录SPI接收数据左移一位问题一、问题描述二、问题分析三、探究原理四、经验总结最近在工作在学习调试SPI的过程中遇到一个问题——接收数据整体向左移了一位(1bit)。SPI数据收发是数据交换,因此接收数据时从第二个字节开始才是有效数据,也就是数据整体向右移一个字节(1byte)。请教前辈之后也没有得到解决,通过在网上查阅前人经验终于解决问题,所以写一个避坑经验总结。实际背景:MCU与一款芯片使用spi通信,MCU作为主机,芯片作为从机。这款芯片采用的是它规定的六线SPI,多了两根线:RDY和INT,这样从机就可以主动请求主机给主机发送数据了。一、问题描述根据从机芯片手
前言一般来说,前端根据后台返回code码展示对应内容只需要在前台判断code值展示对应的内容即可,但要是匹配的code码比较多或者多个页面用到时,为了便于后期维护,后台就会使用字典表让前端匹配,下面我将在微信小程序中通过wxs的方法实现这个操作。为什么要使用wxs?{{method(a,b)}}可以看到,上述代码是一个调用方法传值的操作,在vue中很常见,多用于数据之间的转换,但由于微信小程序诸多限制的原因,你并不能优雅的这样操作,可能有人会说,为什么不用if判断实现呢?但是if判断的局限性在于如果存在数据量过大时,大量重复性操作和if判断会让你的代码显得异常冗余。wxswxs相当于是一个独立