Skip to content

LENGTH / CHAR_LENGTH / OCTET_LENGTH ​

长度函数家族共 5 个成员(含 bit_length、length(bytea)),它们看似都叫「长度」,实际回答的是三个不同问题:有几个字符、占几个字节、占几个比特。选错一个,业务校验就会在中文、Emoji、变长编码上翻车。本节将把字符与字节的边界讲透,并给出校验、截断、估算存储三类场景的正确写法。

前置知识

阅读本节前,建议先了解:SUBSTRING / LEFT / RIGHT / TRIM

基础概念 ​

概念术语说明
字符(Character)Char解码后的最小可见单元,一个汉字算 1 个
码点(Code Point)Code PointUnicode 编号,如 U+4E2D 表示「中」
字节(Byte)Octet存储单位,UTF-8 下汉字通常占 3 字节
比特(Bit)Bit位,1 字节 = 8 比特
变长编码Variable-Length EncodingUTF-8 中不同字符占不同字节数
组合字符Combining Character由基础字符 + 附加符号组成,字符数为 1 但码点多于 1
字符长度语义Character Length业务常说的「长度限制」
字节长度语义Octet Length协议、存储、网络传输关心的长度

五个长度函数的行为对照(以 '中国' 和 'a' 为例):

函数返回值含义'中国''a'典型用途
length(text)字符数21业务长度校验
char_length(text)字符数(SQL 标准名)21同上,可移植性更好
character_length(text)同 char_length21同上,全拼写法
octet_length(text)字节数61存储估算、协议对账
bit_length(text)比特数488极少使用
length(bytea)字节数——二进制数据长度

关键要点:length 是重载函数。当入参是 text 时它算字符,当入参是 bytea 时它算字节:

sql
SELECT
  length('中国'::text)                     AS len_text,      -- 字符数:2
  length('中国'::bytea)                    AS len_bytea,     -- 字节数:6
  octet_length('中国'::text)               AS octet_text,    -- 字节数:6
  bit_length('中国'::text)                 AS bit_text;      -- 比特数:48
text
 len_text | len_bytea | octet_text | bit_text
----------+-----------+------------+----------
        2 |         6 |          6 |       48

语法与用法 ​

sql
-- 字符长度
length(str)               -- text 入参时等价于 char_length
char_length(str)          -- SQL 标准
character_length(str)     -- SQL 标准全拼

-- 字节长度
octet_length(str)         -- 对 text / bytea 均返回字节数
length(str::bytea)        -- 转二进制后再取长度

-- 比特长度
bit_length(str)           -- 等价于 octet_length * 8

-- 二进制长度
length(bytea)

最小可运行示例:

sql
SELECT
  length('PostgreSQL')            AS en_len,
  length('数据库')                 AS cn_len,
  octet_length('PostgreSQL')      AS en_bytes,
  octet_length('数据库')           AS cn_bytes,
  char_length('数据库')            AS cn_chars,
  bit_length('数据库')             AS cn_bits;
text
 en_len | cn_len | en_bytes | cn_bytes | cn_chars | cn_bits
--------+--------+----------+----------+----------+---------
     10 |      3 |       10 |        9 |        3 |      72

准备本节贯穿使用的示例表:

sql
CREATE TABLE users (
  id          INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  username    TEXT        NOT NULL,
  full_name   TEXT,
  bio         TEXT,                       -- 简介,限 100 字符
  phone       TEXT,
  avatar_note TEXT,
  created_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);

INSERT INTO users (username, full_name, bio, phone) VALUES
  ('zhangsan', '张三',       'DBA,专注 PostgreSQL 18 性能优化', '13800000001'),
  ('lisi',     '李四',       '后端开发',                          '13800000002'),
  ('wangwu',   '王五👨‍💻',    '全栈工程师,热爱开源与写作,喜欢咖啡', NULL),
  ('zhaoliu',  '赵六',       NULL,                                '13800000004'),
  ('sunqi',    '孙七',       '数据库内核研发(PG / MySQL)',       '13800000005');
sql
-- 对比:同一个 bio 字段的字符数与字节数
SELECT
  id,
  length(bio)          AS chars,
  octet_length(bio)     AS bytes,
  round(octet_length(bio)::numeric / nullif(length(bio), 0), 2) AS bytes_per_char
FROM users
ORDER BY id;
text
 id | chars | bytes | bytes_per_char
----+-------+-------+----------------
  1 |    26 |    62 |           2.38
  2 |     4 |    12 |           3.00
  3 |    17 |    51 |           3.00
  4 |       |       |
  5 |    16 |    48 |           3.00

第 1 行 bytes_per_char = 2.38——因为混合了 ASCII 字符(1 字节)与汉字(3 字节)。这个指标是判断文本「偏英文还是偏中文」的快捷方式。

详细说明 ​

字符数:length 与 char_length ​

两者对 text 入参完全等价,选择取决于是否需要 SQL 标准可移植性:

sql
SELECT
  length('数据库')        AS by_length,
  char_length('数据库')    AS by_char_length,
  character_length('数据库') AS by_full,
  length('abc')            AS ascii_len,
  length('')               AS empty_len,
  length(NULL)             AS null_len;
text
 by_length | by_char_length | by_full | ascii_len | empty_len | null_len
-----------+----------------+---------+-----------+-----------+----------
         3 |              3 |       3 |         3 |         0 |

三个必须记住的结论:

  1. length('') 返回 0,而 length(NULL) 返回 NULL。做非空校验时,length(col) = 0 与 col IS NULL 是两件不同的事;
  2. length 按字符计,length('中国') = 2,不是 6;
  3. length 与 char_length 都是 IMMUTABLE,可用于表达式索引。

验证易变性:

sql
SELECT
  p.proname,
  CASE p.provolatile
    WHEN 'i' THEN 'IMMUTABLE'
    WHEN 's' THEN 'STABLE'
    WHEN 'v' THEN 'VOLATILE'
  END AS volatility
FROM pg_proc p
WHERE p.proname IN ('length', 'char_length', 'character_length', 'octet_length', 'bit_length')
  AND p.pronamespace = 'pg_catalog'::regnamespace
  AND pg_get_function_arguments(p.oid) LIKE '%text%'
ORDER BY p.proname, pg_get_function_arguments(p.oid);
text
     proname      | volatility
------------------+------------
 bit_length       | IMMUTABLE
 char_length      | IMMUTABLE
 character_length | IMMUTABLE
 length           | IMMUTABLE
 octet_length     | IMMUTABLE

字节数:octet_length ​

octet_length 是判断「存储与传输成本」的唯一正确工具:

sql
SELECT
  octet_length('a')                AS ascii_1,      -- 1
  octet_length('中')                AS cn_1,         -- 3(UTF-8)
  octet_length('€')                 AS euro,         -- 3(U+20AC)
  octet_length('😀')                AS emoji,        -- 4(U+1F600,超出 BMP)
  octet_length(E'\n')              AS newline,      -- 1
  octet_length('')                 AS empty_len;    -- 0
text
 ascii_1 | cn_1 | euro | emoji | newline | empty_len
---------+------+------+-------+---------+-----------
       1 |    3 |    3 |     4 |       1 |         0

UTF-8 的编码长度规律:

码点范围字节数示例
U+0000 ~ U+007F1a、0、_
U+0080 ~ U+07FF2é、ß、西里尔字母
U+0800 ~ U+FFFF3汉字、日文、韩文
U+10000 ~ U+10FFFF4Emoji、生僻汉字

若数据库编码不是 UTF-8(如 GBK 或 SQL_ASCII),字节数会完全不同,务必先确认:

sql
SELECT
  current_database()                              AS db,
  pg_encoding_to_char(encoding)                     AS encoding,
  datcollate,
  datctype
FROM pg_database
WHERE datname = current_database();
text
  db  | encoding |  datcollate  |   datctype
------+----------+--------------+--------------
 demo | UTF8     | zh_CN.UTF-8  | zh_CN.UTF-8

varchar(n) 的 n 按字符计 ​

这是最容易被误解的一处:VARCHAR(10) 允许 10 个字符,而不是 10 个字节。

sql
CREATE TEMP TABLE t_len (a VARCHAR(10), b CHAR(4));

-- 可以插入 10 个汉字(占 30 字节)
INSERT INTO t_len (a, b) VALUES ('十个汉字测试长度呀', '中国');

SELECT length(a) AS chars, octet_length(a) AS bytes FROM t_len;
text
 chars | bytes
-------+-------
    10 |    30
sql
-- 错误!超过 10 个字符会被拒绝(不是按字节)
INSERT INTO t_len (a) VALUES ('这是十一个汉字测试呀');
text
ERROR:  value too long for type character varying(10)

而 CHAR(4)(定长)的 n 同样按字符计,且存储时会用空格补齐:

sql
SELECT
  length(b)         AS char_len_trimmed,   -- 去尾空格后
  octet_length(b)   AS octet,              -- 补齐后
  b || '|'          AS appended;           -- 拼接时尾空格通常被裁剪
FROM t_len;
text
 char_len_trimmed | octet | appended
------------------+-------+----------
                2 |     4 | 中国|

注意 CHAR(4) 存 '中国'(2 字符)时占用 4 个字符位,但 length(b) 返回 2(尾空格被裁剪),octet_length(b) 返回 4(补齐为 4 字节)。char 类型的这一双重性历史上造成过很多 bug,现代建模应优先用 text + CHECK 约束。

length 与 Emoji、组合字符 ​

字符长度的边界在于「组合字符」:某些可见符号由多个码点组成。

sql
SELECT
  '👨‍💻'                    AS zwj_emoji,
  length('👨‍💻')            AS len_zwj,
  octet_length('👨‍💻')       AS octet_zwj,
  length('é')               AS len_precomposed,   -- U+00E9,单码点
  length('e' || U&'\0301')  AS len_combined_char; -- e + 组合重音
text
 zwj_emoji | len_zwj | octet_zwj | len_precomposed | len_combined_char
-----------+---------+-----------+-----------------+-------------------
 👨‍💻        |       3 |        11 |               1 |                 2
  • '👨‍💻'(程序员 Emoji)由「男人」+ 零宽连接符 + 「电脑」3 个码点组成,length 返回 3 而不是 1;
  • 'é' 若用组合形式(e + 组合重音)输入,length 返回 2 而不是 1。

这意味着「字符数」不等于「人类感知的可见字符数」(字素,Grapheme)。若业务严格按用户感知计数(如微博字数限制),PostgreSQL 内置函数无法直接满足,需要应用层用 ICU 的字素切分能力处理。

一个实用的近似手段是 normalize 做 Unicode 规范化,把组合字符合并为预组合形式:

sql
SELECT
  length('e' || U&'\0301')                            AS before_norm,   -- 2
  length(normalize('e' || U&'\0301', NFC))             AS after_nfc,     -- 1
  length(normalize('é', NFD))                          AS to_nfd;        -- 2
text
 before_norm | after_nfc | to_nfd
-------------+-----------+--------
           2 |         1 |      2

NFC(规范合成)把 e + 组合重音合并为单码点 é,长度从 2 变 1。处理用户输入的长度校验前,先规范化能显著减少边界争议。

length 与 trim 的组合陷阱 ​

长度校验前是否修剪,会导致完全不同的结果:

sql
SELECT
  length('  abc  ')                  AS raw_len,      -- 7
  length(btrim('  abc  '))           AS trimmed_len,  -- 3
  length(nullif(btrim('   '), ''))   AS empty_to_null;-- NULL
text
 raw_len | trimmed_len | empty_to_null
---------+-------------+---------------
       7 |           3 |

校验顺序固定为「先修剪 → 再判空 → 再算长度」:

sql
-- 推荐的长度校验写法
SELECT
  id,
  btrim(bio)                                  AS clean_bio,
  length(btrim(bio))                          AS char_len,
  length(btrim(bio)) BETWEEN 1 AND 100        AS len_ok
FROM users
WHERE bio IS NOT NULL
ORDER BY id;
text
 id |              clean_bio               | char_len | len_ok
----+--------------------------------------+----------+--------
  1 | DBA,专注 PostgreSQL 18 性能优化      |       26 | t
  2 | 后端开发                              |        4 | t
  3 | 全栈工程师,热爱开源与写作,喜欢咖啡     |       17 | t
  5 | 数据库内核研发(PG / MySQL)           |       16 | t

长度与索引:表达式索引与 CHECK 约束 ​

length 是 IMMUTABLE,因此可以在表达式索引与 CHECK 约束中使用:

sql
-- 用 CHECK 约束把长度规则下沉到数据库层
ALTER TABLE users
  ADD CONSTRAINT ck_users_bio_len CHECK (bio IS NULL OR length(btrim(bio)) <= 100);

ALTER TABLE users
  ADD CONSTRAINT ck_users_username_len CHECK (length(username) BETWEEN 3 AND 32);
sql
-- 用表达式索引支撑「按长度筛选」的查询
CREATE INDEX idx_users_bio_len ON users ((length(bio)));
ANALYZE users;

EXPLAIN (COSTS OFF)
SELECT id FROM users WHERE length(bio) > 15;
text
                        QUERY PLAN
----------------------------------------------------------
 Index Scan using idx_users_bio_len on users
   Index Cond: (length(bio) > 15)

注意约束与索引都要保证表达式完全一致——上面索引用 length(bio),而约束用 length(btrim(bio)),两者不能互相复用。

用 CHECK 约束拦截超长输入,比在应用层「尽量校验」可靠得多:

sql
-- 验证约束生效
UPDATE users SET bio = repeat('长', 101) WHERE id = 2;
text
ERROR:  new row for relation "users" violates check constraint "ck_users_bio_len"
DETAIL:  Failing row contains (2, lisi, 李四, 长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长, 13800000002, null, 2026-09-24 10:00:00+08).

长度与性能:对大文本调用有何代价 ​

length(text) 需要遍历整个字符串解码计数,时间复杂度 O(n)。对少量小文本毫无影响,但在大表上做全表 length 计算会有明显代价:

sql
-- 不推荐:对 100 万行的大文本逐行计算长度
SELECT count(*) FROM big_docs WHERE length(content) > 1000;

-- 推荐:若只是判断「是否超长」,可先用便宜的短路条件
SELECT count(*) FROM big_docs
WHERE content IS NOT NULL AND length(content) > 1000;

PostgreSQL 对 text 长度有一个便宜的短路优化——varlena 头里存了总字节数,因此 octet_length 是 O(1) 的,而 length 需要真正扫描计数:

函数复杂度说明
octet_length(text)O(1)直接读取 varlena 头中的长度字段
bit_length(text)O(1)基于 octet_length
length(text)O(n)需逐字符解码计数
char_length(char(n))O(1)定长类型长度已知

若业务只关心「是否超过某个字节阈值」,用 octet_length 更快。验证两者差异:

sql
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT count(*) FROM users WHERE octet_length(bio) > 20;

EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT count(*) FROM users WHERE length(bio) > 20;
text
-- octet_length
 Aggregate (actual rows=1 loops=1)
   ->  Seq Scan on users (actual rows=1 loops=1)
         Filter: (octet_length(bio) > 20)

-- length
 Aggregate (actual rows=1 loops=1)
   ->  Seq Scan on users (actual rows=1 loops=1)
         Filter: (length(bio) > 20)

小表下差异不明显,但在 TB 级文本表上,octet_length 的 O(1) 特性可以带来显著差异。

数组的长度:cardinality 与 array_length ​

length 只接受字符串与 bytea,没有数组重载,对数组要换用专门的函数:

sql
-- 错误!length 没有数组重载,会尝试隐式转换后报错
SELECT length(ARRAY['a', 'b', 'c']);
text
ERROR:  function length(text[]) does not exist
HINT:  No function matches the given name and argument types. You might need to add explicit type casts.

数组长度要用 cardinality(基数)或 array_length:

函数返回值空数组时说明
cardinality(anyarray)所有维度的元素总数0推荐,最直观
array_length(anyarray, dim)指定维度的长度NULL维度越界也返回 NULL
array_ndims(anyarray)维度数NULL一维数组返回 1

两者在空数组上的差异是最经典的陷阱:

sql
-- 空数组下 cardinality 返回 0,array_length 返回 NULL
SELECT
  cardinality(ARRAY[]::text[])        AS card_empty,
  array_length(ARRAY[]::text[], 1)    AS arrlen_empty,
  cardinality(ARRAY['a', 'b', 'c'])   AS card_three,
  array_length(ARRAY['a', 'b', 'c'], 1) AS arrlen_three;
text
 card_empty | arrlen_empty | card_three | arrlen_three
------------+--------------+------------+--------------
          0 |              |          3 |            3

用 array_length 做 > 0 判断时,空数组因 NULL 会被静默过滤掉,与字符串那套「NULL 与 '' 分开处理」的教训完全一致:

sql
-- 错误!空数组因 array_length 返回 NULL 而被漏掉
SELECT count(*) FROM products WHERE array_length(tags, 1) > 0;

-- 正确:用 cardinality,空数组返回 0 能正确参与比较
SELECT count(*) FROM products WHERE cardinality(tags) > 0;

多维数组时 cardinality 是各维度长度之积,需按维度取值时只能用 array_length:

sql
-- 多维数组:cardinality 返回总元素数,array_length 取单维
SELECT
  cardinality(ARRAY[[1, 2, 3], [4, 5, 6]])      AS card_2d,
  array_length(ARRAY[[1, 2, 3], [4, 5, 6]], 1)  AS dim1,
  array_length(ARRAY[[1, 2, 3], [4, 5, 6]], 2)  AS dim2,
  array_length(ARRAY[[1, 2, 3], [4, 5, 6]], 3)  AS dim3_out_of_range;
text
 card_2d | dim1 | dim2 | dim3_out_of_range
---------+------+------+-------------------
       6 |    2 |    3 |

integer[3] 这种声明里的长度会被 PostgreSQL 完全忽略,它不构成约束,真正的限制要自己写:

sql
-- 不推荐:声明里的 3 只是文档,不产生任何约束
CREATE TABLE bad_tags (id integer, tags text[3]);

-- 推荐:用 cardinality 写成 CHECK 约束,才是真约束
CREATE TABLE products (
  id    integer PRIMARY KEY,
  name  text    NOT NULL,
  tags  text[]  NOT NULL DEFAULT '{}',
  CONSTRAINT ck_products_tags_len CHECK (cardinality(tags) <= 3)
);
sql
-- 验证约束生效
INSERT INTO products (id, name, tags)
VALUES (1, 'T 恤', ARRAY['服装', '上衣', '夏季', '促销']);
text
ERROR:  new row for relation "products" violates check constraint "ck_products_tags_len"
DETAIL:  Failing row contains (1, T 恤, {服装,上衣,夏季,促销}).

同理,索引与聚合也走数组自己的路径:数组元素计数用 cardinality,展开成行统计用 unnest 后 count(*),二者语义不同。

实战示例 ​

场景一:校验并统计用户输入 ​

需求:bio 限制 100 字符,username 限制 3~32 字符,需要一次性找出所有不合规数据。

sql
-- 步骤 1:定义校验规则并分类统计
SELECT
  count(*)                                                             AS total,
  count(*) FILTER (WHERE length(username) < 3)                         AS username_too_short,
  count(*) FILTER (WHERE length(username) > 32)                        AS username_too_long,
  count(*) FILTER (WHERE bio IS NOT NULL AND length(btrim(bio)) > 100) AS bio_too_long,
  count(*) FILTER (WHERE bio IS NOT NULL AND btrim(bio) = '')           AS bio_blank
FROM users;
text
 total | username_too_short | username_too_long | bio_too_long | bio_blank
-------+--------------------+-------------------+--------------+-----------
     5 |                  0 |                 0 |            0 |         0
sql
-- 步骤 2:列出可能的「隐形超长」——字符数合规但字节数惊人
SELECT
  id,
  username,
  length(bio)        AS chars,
  octet_length(bio)  AS bytes,
  mb_bytes
FROM (
  SELECT id, username, bio, round(octet_length(bio) / 1024.0, 3) AS mb_bytes FROM users
) t
WHERE octet_length(bio) > 30
ORDER BY bytes DESC;
text
 id | username | chars | bytes | mb_bytes
----+----------+-------+-------+----------
  1 | zhangsan |    26 |    62 |    0.061
  3 | wangwu   |    17 |    51 |    0.050
  5 | sunqi    |    16 |    48 |    0.047
sql
-- 步骤 3:估算整列存储占用(含 varlena 头部开销)
SELECT
  pg_size_pretty(sum(octet_length(bio))::bigint)                       AS raw_text,
  pg_size_pretty(sum(octet_length(bio) + 4)::bigint)                   AS with_header,
  pg_size_pretty(pg_total_relation_size('users'))                      AS table_total,
  pg_size_pretty(pg_relation_size('users'))                            AS table_main
FROM users;
text
 raw_text | with_header | table_total | table_main
----------+-------------+-------------+------------
 161 bytes | 173 bytes  | 8192 kB     | 8192 kB

小表下「1 页 = 8 KB」是最小单位,所以 pg_relation_size 直接显示 8192 kB。

场景二:按字节长度截断以适配外部系统 ​

需求:对接一个只接受 64 字节的旧系统字段,必须按字节截断,且不能把 UTF-8 字符切成半个。

sql
-- 步骤 1:先观察错位截断的后果(用 bytea 演示)
SELECT
  convert_from(substring(convert_to('中文测试内容', 'UTF8') FROM 1 FOR 4), 'UTF8') AS bad_cut;
text
 bad_cut
---------
 中

只取了 4 字节(1 个完整汉字 3 字节 + 1 个残缺字节),convert_from 丢弃了残缺部分。这种做法不可靠。

sql
-- 步骤 2:正确的按字节截断函数(保证不切坏字符)
CREATE OR REPLACE FUNCTION truncate_bytes(str text, max_bytes integer)
RETURNS text
LANGUAGE plpgsql
IMMUTABLE
AS $$
DECLARE
  result text := '';
  ch     text;
BEGIN
  IF str IS NULL OR max_bytes <= 0 THEN
    RETURN NULL;
  END IF;

  FOR ch IN SELECT regexp_split_to_table(str, '') LOOP
    EXIT WHEN octet_length(result) + octet_length(ch) > max_bytes;
    result := result || ch;
  END LOOP;

  RETURN result;
END $$;

SELECT
  '中文测试内容abc'                              AS raw,
  octet_length('中文测试内容abc')                 AS raw_bytes,
  truncate_bytes('中文测试内容abc', 8)            AS cut8,
  octet_length(truncate_bytes('中文测试内容abc', 8)) AS cut8_bytes,
  truncate_bytes('中文测试内容abc', 64)           AS cut64;
text
      raw       | raw_bytes |  cut8  | cut8_bytes |      cut64
----------------+-----------+--------+------------+-----------------
 中文测试内容abc |        21 | 中文   |          6 | 中文测试内容abc

cut8 得到 中文(6 字节),因为再加一个汉字会超过 8 字节,函数正确地停止在字符边界上。

sql
-- 步骤 3:在表上验证截断效果
SELECT
  id,
  bio                                                AS raw,
  length(bio)                                        AS chars,
  octet_length(bio)                                  AS bytes,
  truncate_bytes(bio, 20)                            AS cut,
  octet_length(truncate_bytes(bio, 20))              AS cut_bytes
FROM users
WHERE bio IS NOT NULL
ORDER BY id;
text
 id |              raw              | chars | bytes |        cut         | cut_bytes
----+-------------------------------+-------+-------+--------------------+-----------
  1 | DBA,专注 PostgreSQL 18 性能优化 |    26 |    62 | DBA,专注 P        |        13
  2 | 后端开发                       |     4 |    12 | 后端开发           |        12
  3 | 全栈工程师,热爱开源与写作,喜欢咖啡 |    17 |    51 | 全栈工程师,热爱开 |        24
  5 | 数据库内核研发(PG / MySQL)    |    16 |    48 | 数据库内核研发(PG |        21

注意 cut_bytes 都 ≤ 20,且都是完整字符。

注意事项 ​

  1. length(NULL) 返回 NULL,length('') 返回 0,做校验时这两个分支必须分别处理:
    sql
    -- 错误!NULL 行会被静默漏掉
    SELECT * FROM users WHERE length(bio) > 0;
    -- 正确
    SELECT * FROM users WHERE bio IS NOT NULL AND length(btrim(bio)) > 0;
  2. length 是重载函数,length(text) 算字符、length(bytea) 算字节,混用会得到相反结论。
  3. 不要用 length 判断字节是否超限,该用 octet_length。
  4. VARCHAR(n) 的 n 是字符数不是字节数,VARCHAR(10) 能存 10 个汉字(30 字节)。
  5. CHAR(n) 的长度语义有双重性:length 会裁剪尾空格,octet_length 则是补齐后长度,容易对不上账。
  6. Emoji 与组合字符会让「字符数」大于「可见字符数」,'👨‍💻' 的 length 是 3,严格按用户感知计数需应用层处理字素。
  7. 组合形式的 é(e + 重音)长度为 2,做长度校验前建议先 normalize(..., NFC)。
  8. length 是 O(n) 而 octet_length 是 O(1),大文本表上做阈值筛选优先用后者。
  9. 校验前必须先 btrim,否则首尾空格会虚增长度、也会让「全是空格」通过校验。
  10. CHECK 约束与表达式索引的表达式必须逐字符一致,length(bio) 与 length(btrim(bio)) 互不复用。
  11. length 不要写在 WHERE 的索引列上,需要按长度筛选时建表达式索引。
  12. 数据库编码非 UTF-8 时字节规律不成立,用 pg_encoding_to_char(encoding) 先确认。
  13. length 不能用于数组,数组长度用 cardinality;用 array_length 判断时空数组返回 NULL,> 0 会把空数组静默漏掉。
  14. text[3] 声明里的长度会被 PostgreSQL 忽略,要真正限制元素个数需写 CHECK (cardinality(col) <= 3)。

最佳实践 ​

  1. 业务长度校验统一用 char_length(SQL 标准名),可移植性优于 length,含义对审核者更直观。
  2. 协议对账、存储估算、网络传输用 octet_length,并记住 octet_length 是 O(1)。
  3. 长度校验固定顺序:btrim → nullif(..., '') → length → 比较阈值。
  4. 把长度规则写成 CHECK 约束,让数据库成为最后一道防线,而不是只依赖应用层。
  5. 建模优先 text + CHECK,避免 char(n) 的补齐语义带来的长度歧义。
  6. 阈值筛选建表达式索引(((length(bio)))),并让查询表达式与索引定义完全一致。
  7. 需要按字节截断时用 convert_to / convert_from 或字符遍历,不要用 substring(col::bytea, ...) 直接切,会切坏多字节字符。
  8. 外部系统对接前先做字符集对账,确认双方编码一致(UTF-8 / GBK),否则字节数无法对比。
  9. 统计文本「英文还是中文」用 octet_length / length 的比值,接近 1 是纯 ASCII,接近 3 是纯汉字。
  10. 把长度校验放在数据入口(导入、表单提交)而非出口,越早拦截成本越低。

下一节

继续学习:UPPER / LOWER / INITCAP

参考链接 ​