凡是有但是-varchar和nvarchar的初步学习之一

发布时间 2023-12-24 08:50:57作者: 济南小老虎

凡是有但是-varchar和nvarchar的初步学习之一


背景

高应用开发, 在涉及到国内国外的问题时,重要的事情有两个:
时区转换, 字符集转换. 

时区转换虽然是很难理清楚, 各种规范不统一的事情
但是字符集转换更加扯淡. 

如果是一种数据库,一种字符集可能还好说
现在面临的是 多种数据库: 开源,商业,信创,分布式...
这时候就已经是一头雾水了. 
自己虽然看了一阵子, 但是依旧是迷迷糊糊的.
所以想先总结一下, 希望后续能够慢慢的将内容夯实

varchar和nvarchar

数据库有varchar和nvarchar
其实一个比较重的区别是:
部分数据库 varchar 后面的数字指的是 这个字段能够存储的字节数.
nvarchar 后面的数字, 基本上指的是能够存储的字符数. 

这里就有一个 凡是有但是. 
Mysql8数据库虽然有 varcahr和nvarcahr, 但是他计算长度限制是按照字符数来进行计算. 
所以就引入了 mysql utf8mb3 和 utf8mb4 下数据库varcahr总长度限制的问题. 

另外神通数据库有这么一个参数, 可以指定varchar的长度限制.  
DEFAULT_VARCHAR_LEN=1
会改变varchar的存储情况. 

Oracle和MySQL的限制问题

SQLServer的限制: 注意我的排序规则是 chinese_prc_ci_as 
varchar时: 
消息 131,级别 15,状态 2,第 6 行
The size (8001) given to the column 'zhaobsh' exceeds the maximum allowed for any data type (8000).
nvarchar时: 
消息 2717,级别 16,状态 2,第 7 行
The size (8000) given to the parameter 'zhaobsh' exceeds the maximum allowed (4000).

数据库会有一些底层的限制

Oracle数据库一开始与SQLServer的限制是一样的 
也是在ZHS16GBK的字符集  varchar(8000) nvarchar(4000)
但是Oracle19c 时进行了一定程度的扩张: 
32767 bytes if MAX_STRING_SIZE = EXTENDED
4000 bytes if MAX_STRING_SIZE = STANDARD

但是据说这个参数修改起来很麻烦:
数据库需要 startup UPGRADE 以升级模式启动,
然后才能修改参数,
alter system set MAX_STRING_SIZE = EXTENDED scope=spfile;
并且需要跑脚本 utl32k.sql,
然后关闭数据库再正常重启数据库。

注意这里面有一个深坑, 如果不改这个参数 oracle12c 以后可以创建一个 3万多长度的varchar列
但是插入数据时 就会被截断到 4000 / 80000 的长度. 

关于字符编码

需要说明, 字符集是字符集, 字符编码格式是字符编码格式. 
只不过国内的 GB2312 GBK GB18030 既是字符集,又是编码格式. 
所以很多人有所混淆. 

unicode 是字符集, 用来进行全世界所有字符的展示
utf-8 是编码格式,用于计算机系统进行存储. 
utf-16 utf-32 是另外两种编码格式, 不通电是针对字符长度的限制是不一样的. 

字符编码的特点

广义的 Unicode 是一个标准,定义了一个字符集以及一系列的编码规则,
即 Unicode 字符集和 UTF-8、UTF-16、UTF-32 等等编码……

Unicode 字符集为每一个字符分配一个码位,例如「知」的码位是 30693,记作 U+77E5(30693 的十六进制为 0x77E5)。
UTF-8 顾名思义,是一套以 8 位为一个编码单位的可变长编码。会将一个码位编码为 1 到 4 个字节:

U+ 0000 ~ U+ 007F: 0XXXXXXX
U+ 0080 ~ U+ 07FF: 110XXXXX 10XXXXXX
U+ 0800 ~ U+ FFFF: 1110XXXX 10XXXXXX 10XXXXXX
U+10000 ~ U+1FFFF: 11110XXX 10XXXXXX 10XXXXXX 10XXXXXX

根据上表中的编码规则,之前的「知」字的码位 U+77E5 属于第三行的范围:

       7    7    E    5    
    0111 0111 1110 0101    二进制的 77E5
--------------------------
    0111   011111   100101 二进制的 77E5
1110XXXX 10XXXXXX 10XXXXXX 模版(上表第三行)
11100111 10011111 10100101 代入模版
   E   7    9   F    A   5

这就是将 U+77E5 按照 UTF-8 编码为字节序列 E79FA5 的过程。反之亦然。

varchar与nvarchar解决的问题

varchar 应该只是按照数据库的字符集进行文件的存储, 只能够存储编码格式,字符集,排序规则内的字符
无法保存非自己字符集内的字符. 

nvarchar 则是将字符按照unicode 等格式进行保存. 
这样的话, 能够保证非标准字符集支持的字符进行展示. 

一定程度下可以实现国际化多语的需求.

其他数据库的情况

PG数据库没有 nvarchar 
他的字符类型就是 varchar char 还有 text 
他应该默认使用utf8国际化支持, 然后 数字表示的是字符长度.

mysql数据库 有nvarchar的类型
但是他的varchar 也是按照字符进行存储, 理论上也是字符, 不是字节. 
mysql的坑在于 utf8mb3和 utf8mb4 

国产数据库里面要看支付的字符集. 
国产信创必须要支持GB18030字符集. 
但是GB18030与UTF8应该不是严格意义上的子集和超集的关系
所以很多表现也与字符集有关系.

UTF8的出现其实比很多数据库的出现要晚一些.
SQLServer其实到了2019的版本才严格支持 utf8 
因为微软的国际化做的其实比utf8要更久和更踏实, 其实sqlserver的历史包袱也是最大的.