当前位置: 首页 > news >正文

SQL LIKE查询性能优化:通配符、索引与字符集的实战博弈

1. 项目概述:为什么你写的LIKE查询总在生产环境“慢得离谱”?

SQL LIKE模式匹配,听起来像数据库入门课里五分钟就能讲完的小知识点——毕竟就一个通配符%和_,加个ESCAPE字符,再套个WHERE子句,不就完事了?但我在金融风控系统做SQL性能优化的第七年,亲眼见过太多团队把LIKE当万能胶水:模糊搜索用户昵称、匹配日志关键词、校验订单号前缀、甚至拿它做实时反欺诈规则引擎。结果呢?一张千万级交易表,一个WHERE order_id LIKE 'TRADE2024%',执行计划里赫然出现全表扫描;一个WHERE user_name LIKE '%张%',直接拖垮整个报表集群的IO队列。这不是语法写错了,而是对LIKE背后的真实成本、索引行为、字符集陷阱和执行引擎机制一无所知。这篇内容不是教你怎么敲出第一个LIKE语句,而是带你拆开数据库内核的“黑盒子”,看清每一次%符号背后发生的磁盘寻道、缓冲区加载、字符比较和索引跳转。它适合三类人:刚写完CRUD就被告知“这个查询要优化”的初级开发;天天看慢SQL报告却找不到根因的DBA;还有那些在面试中被问“LIKE能走索引吗”而支吾半天的求职者。我们不讲抽象理论,只聊实测数据、真实执行计划、可抄的建索引命令,以及我踩过三次才记住的字符集坑——比如MySQL用utf8mb4时,LIKE 'abc%'能走索引,但LIKE 'abc_%'在某些版本里会悄悄退化成全表扫描,原因藏在排序规则collation的权重计算里。

1.1 核心需求解析:你真正需要的不是语法,而是“可控性”

很多人搜“SQL LIKE教程”,实际想解决的是这四个具体问题:第一,怎么让模糊搜索不拖垮线上服务;第二,如何确保用户输入的任意字符串(比如带单引号、反斜杠、中文括号)不会导致SQL注入或语法报错;第三,在PostgreSQL里用ILIKE做大小写不敏感匹配时,为什么比普通LIKE慢3倍;第四,当业务要求“匹配邮箱域名后缀”(如'%@gmail.com')时,有没有比LIKE更高效且安全的替代方案。这些都不是语法手册能回答的。它们直指三个底层维度:执行路径的确定性(能否稳定走索引)、输入边界的鲁棒性(如何处理恶意或异常输入)、跨引擎的行为一致性(MySQL/PostgreSQL/SQL Server对相同LIKE表达式的执行策略差异)。所以本文所有示例都基于真实压测场景:用sysbench生成500万行用户数据,字段username VARCHAR(64),字符集utf8mb4_unicode_ci,在8核16G的云服务器上反复执行并捕获EXPLAIN ANALYZE输出。所有结论都有执行时间、IO次数、缓冲区命中率三重验证,拒绝“理论上可行”。

1.2 影响范围与技术边界:哪些场景LIKE天生就不该用

必须先划清红线:LIKE不是万能的,有些需求从设计源头就该换方案。比如实时日志流中的关键词告警,用log_content LIKE '%ERROR%'去扫每秒万级的日志记录,本质是用OLTP数据库干了OLAP的事——正确解法是接入Elasticsearch或ClickHouse,用倒排索引加速全文检索。再比如匹配IP地址段,写ip_addr LIKE '192.168.%'看似合理,但IPv4地址有标准的CIDR表示法,用INET_ATON(ip_addr) BETWEEN INET_ATON('192.168.0.0') AND INET_ATON('192.168.255.255')不仅语义清晰,还能走整数索引。还有更隐蔽的陷阱:在Oracle中对CLOB字段用LIKE,即使前面有WHERE status = 'ACTIVE'这样的高选择性条件,优化器也可能因CLOB无法建传统B-Tree索引而放弃使用status索引。这些不是LIKE的bug,而是它作为字符串前缀/后缀/子串匹配原语的固有局限。本文聚焦在它“能胜任”的领域:结构化字段的精确模式控制,比如用户名前缀搜索、订单号固定格式校验、产品编码分组匹配。超出这个范围的,我会明确告诉你“这里该换方案”,并给出迁移路径。

2. 核心细节解析与实操要点:通配符、索引、字符集的三角博弈

LIKE的表面语法极简,但它的执行效率由三个要素动态博弈决定:通配符位置、索引结构、字符集排序规则。忽略任一环,都可能让查询从毫秒级飙升到分钟级。下面用真实测试数据拆解这个三角关系。

2.1 通配符位置决定索引命运:为什么'abc%'能走索引,'%abc'不能

先看最常被误解的案例。创建一张测试表:

CREATE TABLE users ( id BIGINT PRIMARY KEY, username VARCHAR(64) NOT NULL, email VARCHAR(128) ); INSERT INTO users SELECT seq, CONCAT('user', seq), CONCAT('user', seq, '@test.com') FROM seq_1_to_1000000; CREATE INDEX idx_username_prefix ON users(username);

执行EXPLAIN SELECT * FROM users WHERE username LIKE 'user123%';,MySQL 8.0的执行计划显示key: idx_username_prefixrows: 1,耗时0.0002秒。但把条件换成username LIKE '%123',执行计划立刻变成type: ALL(全表扫描),rows: 1000000,耗时1.8秒。为什么?因为B-Tree索引的本质是有序树结构,它只能高效支持“从某个起点开始向右查找”。'user123%'等价于范围查询username >= 'user123' AND username < 'user124',索引可以快速定位到'user123'这个分支节点,然后遍历其右侧所有子节点。而'%123'要求匹配所有以'123'结尾的字符串,比如'aaa123'、'bbb123'、'ccc123',它们在B-Tree中完全分散在不同分支,索引无法提供任何顺序线索,只能逐行扫描。PostgreSQL表现类似,但有个关键差异:它支持函数索引。你可以为后缀匹配创建表达式索引:CREATE INDEX idx_username_suffix ON users (reverse(username));,然后查询改写为WHERE reverse(username) LIKE reverse('%123'),即WHERE reverse(username) LIKE '321%'——这样就把后缀问题转化成了前缀问题,成功走索引。我在某电商后台实测,百万级商品标题后缀搜索,函数索引将响应时间从2.3秒压到0.015秒。

提示:'a%b'这种中间带通配符的模式,无论MySQL还是PostgreSQL都无法走传统B-Tree索引。它必须扫描所有以'a'开头的记录,再逐个检查是否包含'b'。此时应考虑全文索引(如MySQL的FULLTEXT)或专用搜索引擎。

2.2 字符集与排序规则:unicode_ci里的隐藏陷阱

字符集不是背景板,它直接改写LIKE的比较逻辑。在MySQL中,utf8mb4_unicode_ciutf8mb4_bin对同一查询的索引行为可能天差地别。创建两张结构相同的表,仅字符集不同:

-- 表A:unicode_ci排序规则 CREATE TABLE users_ci ( id BIGINT PRIMARY KEY, name VARCHAR(64) COLLATE utf8mb4_unicode_ci, INDEX idx_name_ci (name) ); -- 表B:binary排序规则 CREATE TABLE users_bin ( id BIGINT PRIMARY KEY, name VARCHAR(64) COLLATE utf8mb4_bin, INDEX idx_name_bin (name) );

插入相同数据后执行SELECT * FROM users_ci WHERE name LIKE 'Jo%';SELECT * FROM users_bin WHERE name LIKE 'Jo%';。表面看结果一致,但执行计划暴露真相:users_cikey_len是256(字节),而users_bin是128。为什么?因为unicode_ci在索引中存储的是归一化后的权重值(weight),用于支持大小写不敏感和重音不敏感比较,这导致索引键变长,同等内存下能缓存的索引页更少,间接增加磁盘IO。更致命的是,在某些MySQL版本中,unicode_ciLIKE 'abc_%'的处理会触发额外的字符权重计算,导致优化器误判选择性,放弃使用索引。而utf8mb4_bin是严格字节比较,LIKE 'abc_%'能稳定走索引,只要_匹配单个字节(对ASCII字符成立)。实测数据:在500万行数据上,users_binname LIKE 'John_'查询耗时0.008秒,users_ci同查询耗时0.42秒,相差52倍。所以,如果你的业务明确要求大小写敏感(如密码盐值、API密钥),强制用_bin排序规则,性能提升立竿见影。

2.3 ESCAPE字符的双重身份:转义符还是性能杀手?

ESCAPE子句常被当作解决%_字面量匹配的银弹,但它可能成为性能隐形杀手。看这个典型场景:用户搜索包含下划线的订单号,如'ORD_2024_001'。开发者写出WHERE order_id LIKE 'ORD\_2024\_%' ESCAPE '\'。语法正确,但执行效率堪忧。因为ESCAPE字符触发了数据库的逐字符解析状态机:优化器无法预判转义后的实际模式,必须在运行时对每个字符判断是否为转义序列,这增加了CPU开销。更糟的是,某些旧版MySQL(5.7之前)对ESCAPE的支持不完善,可能导致索引失效。我的建议是:优先用参数化查询+字符串预处理替代ESCAPE。例如在应用层,将用户输入的'ORD_2024_001'中的'_'替换为'[ _ ]'(方括号表达式),然后写WHERE order_id LIKE 'ORD[ _ ]2024[ _ ]%'。方括号是标准SQL的字符类,多数数据库能将其编译为确定性状态机,且不影响索引使用。实测对比:在PostgreSQL 14中,ESCAPE '\'版本平均耗时12ms,而方括号版本仅3.1ms,且缓冲区命中率高17%。

3. 实操过程与核心环节实现:从建表到压测的完整链路

纸上谈兵不如真刀真枪。下面以一个真实的电商用户搜索功能为例,展示如何从零构建高性能LIKE查询链路。需求:支持用户按昵称前缀搜索(如输入“张”显示“张三”、“张小花”),响应时间<100ms,QPS>500。

3.1 建表与索引设计:不止是加个INDEX那么简单

首先,字段定义必须精准。错误示范:username VARCHAR(255)——255是历史遗留魔数,实际昵称极少超32字。过大VARCHAR会导致索引页碎片化,降低缓存效率。正确做法:

CREATE TABLE users_search ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(32) NOT NULL, -- 业务侧已校验长度 username_normalized VARCHAR(32) GENERATED ALWAYS AS (LOWER(username)) STORED, -- 生成列 created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_username_prefix (username), -- 前缀搜索主索引 INDEX idx_username_norm (username_normalized) -- 大小写不敏感搜索索引 );

关键点解析:

  • VARCHAR(32)而非TEXT:B-Tree索引对TEXT字段支持有限,且32字足够覆盖99.7%的中文昵称(按UTF8MB4,32字节=8个汉字)。
  • GENERATED ALWAYS AS (LOWER(username)) STORED:生成列将原始用户名转小写并持久化存储。相比每次查询用LOWER(username) LIKE LOWER(?),它避免了运行时函数计算,且username_normalized列可建索引。
  • 双索引策略:idx_username_prefix服务LIKE '张%'idx_username_norm服务LIKE 'zhang%'(用户输入拼音时)。两者互不干扰,根据查询条件自动选择。

注意:生成列需MySQL 5.7+,且STORED关键字不可省略,否则索引无法建立。PostgreSQL用CREATE INDEX ... ON table ((lower(username)))实现等效功能。

3.2 查询语句编写:参数化、防注入、性能兜底三位一体

应用代码中,绝不能拼接SQL。以Python Flask为例:

# 错误:字符串拼接(SQL注入高危!) query = f"SELECT * FROM users_search WHERE username LIKE '{keyword}%'" # 正确:参数化查询 + 输入清洗 def search_users(keyword: str) -> List[dict]: # 1. 长度限制:防超长关键词拖垮查询 if len(keyword) > 16: keyword = keyword[:16] # 2. 特殊字符转义:将用户输入的%和_转为字面量 # 使用标准SQL的ESCAPE,但提前处理,避免运行时解析开销 escaped_keyword = keyword.replace('\\', '\\\\').replace('%', '\%').replace('_', '\_') # 3. 执行参数化查询 cursor.execute( "SELECT id, username FROM users_search " "WHERE username LIKE %s ESCAPE '\\' " "ORDER BY id LIMIT 20", (escaped_keyword + '%',) ) return cursor.fetchall()

这里的关键技巧是提前转义。把%_的转义逻辑放在应用层,而不是依赖数据库的ESCAPE运行时解析。这样数据库收到的就是纯字符串'张\%',优化器能准确估算选择性,稳定走索引。同时,LIMIT 20是硬性兜底,防止用户输入空字符串''导致全表扫描返回海量数据。

3.3 压测与调优:用真实数据验证每一步

用sysbench模拟真实负载。先准备500万行测试数据:

sysbench oltp_read_only \ --db-driver=mysql \ --mysql-host=127.0.0.1 \ --mysql-port=3306 \ --mysql-user=root \ --mysql-password=pass \ --mysql-db=testdb \ --tables=1 \ --table-size=5000000 \ --threads=64 \ --time=300 \ --report-interval=10 \ run

重点观察SHOW PROFILE FOR QUERY N;输出。当发现Sending data阶段耗时占比超60%,说明IO瓶颈;若Copying to tmp table频繁,说明排序未走索引。针对我们的users_search表,压测结果显示:

  • keyword='张':平均延迟8.2ms,QPS 620,Handler_read_next(索引遍历)次数为23次/查询;
  • keyword='张三':平均延迟3.1ms,QPS 780,Handler_read_next降为5次/查询;
  • keyword=''(空输入):触发LIMIT 20兜底,延迟稳定在1.5ms,但Handler_read_first(索引首行定位)达1次/查询,证明未全表扫描。

这验证了索引设计的有效性。如果keyword='张'Handler_read_next高达500000次,说明索引未生效,需检查字符集或查询条件是否隐式转换。

3.4 跨数据库适配:MySQL、PostgreSQL、SQL Server的实战差异

同一需求,在不同数据库的最优解不同。整理成速查表:

场景MySQL 8.0+PostgreSQL 14+SQL Server 2019
前缀搜索LIKE 'abc%'INDEX(col)直接生效INDEX(col)直接生效INDEX(col)直接生效
后缀搜索LIKE '%abc'无原生解,用REVERSE()函数索引CREATE INDEX ON t((reverse(col)))INDEX(REVERSE(col))(需SQL Server 2016+)
大小写不敏感COLLATE utf8mb4_0900_as_cs或生成列ILIKELOWER(col) LIKE LOWER(?)COLLATE SQL_Latin1_General_CP1_CI_AS
正则替代REGEXP 'pattern'(性能差,慎用)~ 'pattern'(支持索引优化)LIKE是唯一标准选项

特别提醒SQL Server用户:它的LIKE优化器对'abc%'支持极好,但对'ab%c'(中间通配符)会尝试使用索引的“跳跃扫描”,实际效果不稳定。我在线上环境遇到过,同样查询在SQL Server 2017中走索引,升级到2019后因统计信息更新策略变化,反而退化为全表扫描。解决方案是显式添加OPTION (RECOMPILE)提示,强制每次生成新执行计划。

4. 常见问题与排查技巧实录:那些文档里不会写的坑

以下是我在客户现场、代码审查、线上故障复盘中总结的TOP5高频问题,附带可立即执行的排查命令和修复方案。

4.1 问题1:明明建了索引,EXPLAIN却显示type: ALL

现象:EXPLAIN SELECT * FROM products WHERE sku LIKE 'ABC%';显示key: NULL
排查三步法

  1. 检查字段类型是否匹配:DESCRIBE products;确认skuVARCHAR而非TEXTTEXT字段在MySQL中无法建前缀索引(除非指定长度,如INDEX(sku(10)))。
  2. 检查字符集隐式转换:SHOW CREATE TABLE products;查看skuCOLLATE。如果WHERE条件中的字符串字面量字符集与字段不同(如字段是utf8mb4,而'ABC%'latin1),MySQL会进行隐式转换,导致索引失效。解决方案:在查询中显式声明字符集,WHERE sku LIKE _utf8mb4'ABC%'
  3. 检查索引长度:SHOW INDEX FROM products;查看Sub_part列。如果sku索引是INDEX(sku(10)),而'ABC%'匹配的字符串长度超10,索引同样失效。此时需重建索引:DROP INDEX idx_sku ON products; CREATE INDEX idx_sku ON products(sku);

实操心得:我曾在一个物流系统中发现,tracking_number VARCHAR(20)字段建了INDEX(tracking_number(10)),但业务要求匹配'SF%'(顺丰单号前缀),而顺丰单号实际是'SF1234567890',前10位'SF12345678'远超'SF%'所需,但索引长度不足导致无法利用。扩大索引长度后,查询从1.2秒降至0.004秒。

4.2 问题2:LIKE查询在开发环境快,上线后慢十倍

现象:本地MySQL 5.7跑LIKE 'test%'20ms,生产MySQL 8.0跑同样查询200ms。
根因分析

  • 开发库数据量小(1万行),生产库大(500万行),但EXPLAIN显示都走索引,为何差距这么大?
  • 关键在缓冲池命中率。执行SHOW ENGINE INNODB STATUS\G,查看BUFFER POOL AND MEMORY部分的Database pagesFree buffers。如果Free buffers长期为0,说明缓冲池满,新页加载需淘汰旧页,增加磁盘IO。
  • 生产库innodb_buffer_pool_size可能设置过小。计算公式:总内存 * 0.7。8G服务器应设为5.6G,而非默认的128M。
    修复命令
-- 动态调整(重启后失效) SET GLOBAL innodb_buffer_pool_size = 5639631872; -- 永久生效,修改my.cnf # innodb_buffer_pool_size = 5G

4.3 问题3:用户输入单引号导致SQL报错

现象:用户搜索O'Reilly,后端报错You have an error in your SQL syntax
安全方案
绝不依赖addslashes()mysql_real_escape_string()(已废弃)。正确姿势是双保险

  1. 应用层:用ORM或数据库驱动的参数化查询,如Python的cursor.execute("SELECT * FROM books WHERE title LIKE %s", (f"{keyword}%",))
  2. 数据库层:对title字段启用STRICT_TRANS_TABLES模式,防止截断注入。
    避坑技巧:如果必须拼接(如动态SQL生成),用标准SQL的QUOTE()函数:
SET @keyword = "O'Reilly"; SET @sql = CONCAT("SELECT * FROM books WHERE title LIKE ", QUOTE(CONCAT(@keyword, '%'))); PREPARE stmt FROM @sql; EXECUTE stmt;

QUOTE()会自动将单引号转义为两个单引号,且返回带引号的字符串,彻底杜绝语法错误。

4.4 问题4:中文LIKE搜索返回乱码或漏匹配

现象:WHERE name LIKE '张%'返回空结果,但表中确有'张三'
诊断流程

  1. 检查连接字符集:SHOW VARIABLES LIKE 'character_set%';确保character_set_clientcharacter_set_connectioncharacter_set_results均为utf8mb4
  2. 检查表字符集:SHOW CREATE TABLE users;确认name字段COLLATEutf8mb4_unicode_ciutf8mb4_bin
  3. 检查数据实际编码:SELECT HEX(name) FROM users WHERE id=1;如果返回E5BCA0E4B889(张三的UTF8MB4十六进制),说明数据正常;若返回C3A7C2B5(Latin1编码的乱码),说明数据入库时字符集不匹配。
    终极修复
-- 修正表字符集(数据不丢失) ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 修正连接字符集(应用配置) # MySQL连接URL添加?characterEncoding=utf8mb4&useUnicode=true

4.5 问题5:LIKE在JOIN中性能断崖式下跌

现象:SELECT u.name FROM users u JOIN orders o ON u.id = o.user_id WHERE u.name LIKE 'John%'比单表查询慢100倍。
优化核心驱动表选择。EXPLAIN显示u表为type: ALLo表为type: ref,说明优化器选错了驱动表。强制users为驱动表:

SELECT /*+ STRAIGHT_JOIN */ u.name FROM users u JOIN orders o ON u.id = o.user_id WHERE u.name LIKE 'John%';

STRAIGHT_JOIN提示告诉优化器按FROM后顺序执行JOIN。实测某社交APP,此提示将关联查询从8.7秒降至0.04秒。PostgreSQL用/*+ Leading(u o) */达到同样效果。

5. 进阶技巧与未来演进:当LIKE不够用时,你的备选武器库

LIKE是SQL的基石,但不是终点。当业务复杂度上升,你需要更强大的工具组合。

5.1 全文索引:从模糊匹配到语义理解

当需求从“找包含关键词的记录”升级为“找最相关的记录”,LIKE就力不从心了。MySQL的FULLTEXT索引支持自然语言模式和布尔模式:

-- 创建全文索引 ALTER TABLE articles ADD FULLTEXT(title, content); -- 布尔模式搜索(支持+ - *等操作符) SELECT * FROM articles WHERE MATCH(title, content) AGAINST('+database +performance -mysql' IN BOOLEAN MODE);

优势在于:自动词干提取('running'匹配'run')、相关性评分(MATCH() AGAINST()返回浮点数)、停用词过滤。我在某技术博客平台实测,全文搜索比LIKE '%database performance%'快12倍,且返回结果按相关性排序,无需应用层二次打分。

5.2 正则表达式:处理复杂模式的终极方案

LIKE只支持%_,而正则支持任意模式。PostgreSQL的~操作符可走索引(需pg_trgm扩展):

-- 启用扩展 CREATE EXTENSION IF NOT EXISTS pg_trgm; -- 创建GIN索引加速正则 CREATE INDEX idx_title_gin ON articles USING GIN (title gin_trgm_ops); -- 查询:匹配以"SQL"开头,后跟数字和"优化"的标题 SELECT * FROM articles WHERE title ~ '^SQL[0-9]+优化$';

pg_trgm将字符串分解为三元组(trigram),索引存储这些三元组,使正则匹配转化为高效的集合交集运算。实测百万级文章标题,复杂正则查询从3.2秒降至0.08秒。

5.3 外部搜索引擎:超越数据库的极限

当数据量超亿级,或需高亮、聚合、同义词扩展时,必须引入Elasticsearch。同步方案推荐:

  • 双写:应用层同时写MySQL和ES,简单但一致性难保证;
  • Binlog监听:用Debezium捕获MySQL变更,实时同步到ES,强一致性,生产首选;
  • Logstash JDBC插件:定时增量同步,适合准实时场景。

我在某新闻APP的实践中,将LIKE '%新冠%'迁移到ES后,搜索响应时间从1.5秒降至0.06秒,且支持拼音搜索(输“xg”匹配“新冠”)、错别字纠正(输“新官”匹配“新冠”)。

6. 我的个人经验总结:那些没写在手册里的真相

在数据库这条路上摸爬滚打十多年,关于LIKE,我有三条刻进骨子里的经验:

第一,永远相信EXPLAIN,而不是相信自己写的SQL。我见过太多资深工程师拍胸脯说“这个LIKE肯定走索引”,结果一跑EXPLAINkey列赫然是NULL。原因五花八门:字段上有函数包装、JOIN顺序被优化器重排、统计信息过期未更新。养成习惯:上线前,对每个LIKE查询必跑EXPLAIN FORMAT=JSON,重点关注key,rows,filtered三个字段。filtered低于10%就要警惕,说明索引选择性差,可能需要调整索引或查询条件。

第二,字符集不是配置项,是性能开关。很多团队把utf8mb4当摆设,建表时不指定COLLATE,结果用默认的utf8mb4_general_ci(已废弃),导致索引键长翻倍、比较逻辑混乱。我的硬性规定:所有新表必须显式声明COLLATE utf8mb4_0900_as_cs(大小写敏感)或utf8mb4_0900_ai_ci(大小写不敏感),并在建表后立即执行ANALYZE TABLE table_name;更新统计信息。

第三,没有银弹,只有权衡。有人问我“到底该用LIKE还是全文索引”,我的回答是:看QPS和延迟SLA。如果搜索QPS<100,延迟要求<500ms,LIKE加合理索引足够;如果QPS>1000,延迟要求<100ms,且需相关性排序,那必须上Elasticsearch。技术选型不是比谁更炫,而是算清楚每种方案的CPU、内存、磁盘、人力成本。我亲手推翻过一个“用Redis Sorted Set模拟全文索引”的方案,因为运维成本太高——每天要人工同步千万级数据,一次同步失败就导致搜索不准,最后还是回归了成熟的ES。

最后分享一个小技巧:在MySQL中,LIKE 'abc%'>= 'abc' AND < 'abd'在语义和性能上完全等价,但后者更透明,能让团队新人一眼看懂执行逻辑。我在代码评审中,会把所有LIKE 'prefix%'强制改写为范围查询,既提升可读性,又规避了某些ORM对LIKE的奇怪处理。技术的价值,最终体现在它让复杂问题变得简单、可预测、可维护。

http://www.jsqmd.com/news/1143621/

相关文章:

  • Git别名实战指南:配置原理、作用域管理与高可用工程实践
  • MATLAB/Python双平台小波时频特征提取工具(含幅值波动量化说明)
  • Cursor双模型协同开发:Claude 4.7与Gemini 3.1动态路由实战
  • Python算法交易系统实战:从行情接入到实盘执行的工业级架构
  • 数据科学家成长路径:八步法背后的业务驱动逻辑
  • openEuler Jenkins SPEC文件检查:RPM包构建规范全解析
  • 大模型一手信息源清单:GitHub、Discord与Marketplace实操指南
  • 小样本分类数据关联检验:Fisher精确检验实战指南
  • OWASP ZAP 2.15.0 进阶测试:DVWA 中/高安全等级下 3 种 CSRF Token 绕过策略
  • OpenClaw本地AI代理框架:Windows原生轻量级Agent运行时详解
  • Excel空格清理全攻略:识别与清除4类隐形空格
  • STM32F429嵌入式示波器实战工程包:带完整驱动、可烧录源码与硬件调试说明
  • SMAPI终极指南:轻松打造属于你的星露谷物语模组世界
  • MAA明日方舟助手:如何用开源自动化工具彻底解放你的游戏时间
  • BilibiliDown:一键下载B站视频,打造你的个人离线视频库
  • DynamoDB单表设计:访问模式驱动的高性能建模实践
  • GHelper终极指南:解锁ROG设备性能封印的轻量级解决方案
  • 蔡司3D扫描仪怎么选?从企业检测需求看供应商实力
  • IQ-TREE:现代系统发育分析的终极高效解决方案
  • 三步永久激活:KMS智能激活工具完整指南
  • Excel嵌套IF实战指南:稳如磐石的生产级逻辑设计
  • Excel合并单元格的真相:视觉美化 vs 数据安全
  • 鬼手剪辑/趣丸千音/智马翻译实测对比:短剧出海到底选谁?
  • 如何轻松搭建个人离线小说库:番茄小说下载器终极指南
  • 3 种主流超参数优化工具对比:Ray Tune vs Optuna vs WandB Sweeps 在 ResNet-50 调参效率实测
  • 小红书内容管理革命:XHS-Downloader如何帮你高效收集灵感素材
  • C++版轻量几何识别工具:支持等边三角形、矩形、正六边形和圆形的实时图像检测与标注
  • 碧蓝航线Alas自动化脚本:让游戏自己玩的终极指南
  • 低代码平台OA协同办公系统构建:从表单引擎到流程自动化的技术架构解析
  • Kimi K2 API接入Claude Code实操指南