MySQL中INSTR(str, substr)返回子串首次出现位置(从1开始),未找到返回0;SQL Server中CHARINDEX(substr, str[, start])参数顺序相反,支持起始位置。

MySQL 用 INSTR 查子串位置,注意参数顺序是「被查字符串, 子串」
INSTR 是 MySQL 的原生函数,返回子串首次出现的起始位置(从 1 开始计数),没找到返回 0。它和多数语言习惯相反:不是 INSTR(haystack, needle),而是 INSTR(str, substr)。
常见错误是把参数顺序写反,比如写成 INSTR('abc', 'a') 看似合理,但实际正确写法就是它——只是容易和 LOCATE 混淆(LOCATE 支持第三个参数指定起始偏移,INSTR 不支持)。
- 想从第 2 个字符开始搜?得先用
SUBSTR(str, 2)截取再查,不能直接传偏移 - 区分大小写取决于字段排序规则(collation),比如
utf8mb4_0900_as_cs下INSTR('Abc', 'a')返回 0 - 空子串
INSTR('hello', '')返回 1(MySQL 行为,不是 bug)
SQL Server 用 CHARINDEX,参数顺序是「子串, 被查字符串, [起始位置]」
CHARINDEX 更接近直觉:子串在前,主串在后,可选第三个参数控制搜索起点。返回值也是从 1 开始的位置,没找到返回 0。
典型误用是漏掉必填参数或混淆索引起点——SQL Server 不接受 CHARINDEX('x', NULL),遇到 NULL 直接返回 NULL,不是报错,容易在 WHERE 条件里意外过滤掉整行。
- 第三个参数是「起始字符位置」,不是「跳过几个字符」,
CHARINDEX('o', 'hello', 3)返回 5(不是 2) - 如果子串为空(
''),SQL Server 返回 0(和 MySQL 不同) - 想查最后一次出现?没有内置函数,得用
REVERSE套一层:LEN(str) - CHARINDEX('x', REVERSE(str)) + 1
跨数据库兼容写法几乎不存在,别硬套
PostgreSQL 用 POSITION('sub' IN str) 或 STRPOS(str, 'sub');Oracle 用 INSTR(str, substr[, start[, nth]]),参数顺序又和 MySQL 不同(支持起始位和第几次出现)。试图用一个 SQL 同时跑通多个库,只会让逻辑变脆弱。
如果项目需要多数据库支持,建议把位置查找逻辑提到应用层(如 Python 的 str.find() 或 Go 的 strings.Index()),数据库只负责传原始字段。否则光是空值处理、大小写敏感、边界返回值(0 vs NULL vs -1)就足够埋坑。
- MySQL 的
INSTR和 Oracle 的INSTR同名但参数不兼容,别凭名字猜行为 - WHERE 中用
INSTR(col, 'x') > 0可以,但等价于col LIKE '%x%',后者可能走索引(取决于 pattern 和引擎) - 用位置结果做
SUBSTRING切片时,务必检查是否为 0,否则 MySQL 可能返回空串,SQL Server 报错「Invalid length parameter」
性能提醒:别在 WHERE 里对大字段反复调用这些函数
这些函数都是逐行计算的标量操作,无法利用索引加速。如果表有千万级数据,且经常执行 WHERE INSTR(content, 'keyword') > 0,响应会明显变慢。
真有高频子串检索需求,应考虑全文索引(MySQL 的 FULLTEXT,PostgreSQL 的 tsvector)或外部方案(Elasticsearch、Doris)。临时应急可以加生成列+索引,比如 MySQL 8.0+:
ALTER TABLE docs ADD COLUMN has_pdf TINYINT AS (INSTR(content, '.pdf') > 0) STORED; CREATE INDEX idx_has_pdf ON docs(has_pdf);
但生成列内容固定,没法支持任意关键词。
最常被忽略的是:函数返回位置后,下一步往往要 SUBSTRING 或 REPLACE,而这些操作本身也开销不小——先确认业务是否真的需要位置数字,还是只需要“是否存在”或“替换后结果”。


















