马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
天天写 SQL,你遇到过哪些「神奇」特性?(分组 / IN vs EXISTS / 性能优化)
【导语】梳理多个容易踩坑的 SQL「特性」:①ONLY_FULL_GROUP_BY 约束——GROUP BY 后 SELECT 不能直接引用非聚合原表列,宽松模式虽能跑但有随机值风险;②NOT IN 子查询含 NULL 会立即返回未定义结果(计数直接为 0),改用 NOT EXISTS 或加 WHERE id IS NOT NULL;③通用性能优化:EXISTS/连接 代替 IN 避免临时表、UNION ALL 代替 UNION 免排序、EXISTS 代替 DISTINCT、极值函数/索引利用等。
核心观点
- ONLY_FULL_GROUP_BY:聚合查询 SELECT 列须为 GROUP BY 列或聚合函数,宽松模式绕过但有随机值风险
- NOT IN 坑:子查询含 NULL 会停止比较返回未定义(计数变 0),用 NOT EXISTS 或过滤 NULL 解决
- 性能优化:EXISTS/连接 替 IN 免临时表;UNION ALL 替 UNION 免排序;EXISTS 替 DISTINCT;善用索引、规避 HAVING 过早写条件
适用场景
适用于 SQL 开发避坑、查询性能优化与数据库迁移(MySQL/Oracle/Snowflake)兼容性场景。
关键信息
(SQL 优化,文中报错示例、NOT IN/NOT EXISTS 对比与执行计划见正文。)
- 由于大Key的内存分配及释放开销变大,直接影响就是导致应用访问Redis的响应变慢;
- 删除时会造成较长时间的阻塞并有可能造成集群主备节点切换【4.0之前的版本有这个问题】;
- 内存占用过多甚至达到maxmemory配置,会造成新写入阻塞或一些不应该被提前删除的Key被逐出,甚至导致OOM发生;
- 并发读请求因为Key过大会可能打满服务器带宽,如果单机多实例部署则同时会影响到该服务器上的其它服务【假设一个bigkey为1MB,客户端每秒访问量为1000,那么每秒产生1000MB的流量】;
- 运维麻烦,比如RedisCluster的数据跨节点均衡,因为均衡迁移原理是通过migrate命令来完成的,这个命令实际是通过dump + restore + del三个命令组合成原子命令完成,如果是bigkey,可能会使迁移失败,而且较慢的migrate也会阻塞Redis正常请求;
- 分片集群RedisCluster中的出现严重的数据倾斜,导致某个节点的内存使用过大;
那么对于已经写入的数据,如何分析找出里面的bigkey进行优化呢?可以通过Redis官方客户端redis-cli的bigkeys参数来定位大Key分布。shell> redis-cli -h 127.0.0.1 -p 18708 -a xxxx --bigkeys -i 0.01[00.00%] Biggest string found so far 'urlcount:www.guprocessorSuccessMid' with 1 bytes[00.01%] Biggest string found so far 'TestDomain:www:config:scheduler' with 3847 bytes[00.03%] Biggest string found so far 'TestDomain:www:config:scheduler' with 211306 bytes[00.88%] Biggest set found so far 'specialTestJobSet:www' with 20 members[01.69%] Biggest list found so far 'TestDomain:www:urlList' with 9762 items[07.13%] Biggest list found so far 'TestDomain:bx:urlList' with 457676 items[07.39%] Biggest set found so far 'specialTestJobSet:www' with 100 members[13.99%] Biggest string found so far 'TestDomain:wwwe:config:scheduler' with 540731 bytes[18.74%] Biggest set found so far 'TestJobSet' with 300 members[58.09%] Biggest string found so far 'TestDomain:wwwrt:config:scheduler' with 739024 bytes[64.19%] Biggest string found so far 'TestDomain:bx:config:scheduler' with 1335468 bytes -------- summary -------Sampled 62522 keys in the keyspace!Total key length in bytes is 2471471 (avg len 39.53)Biggest list found 'TestDomain:bx:urlList' has 457676 itemsBiggest string found 'TestDomain:bx:config:scheduler' has 1335468 bytesBiggest set found 'TestJobSet' has 300 members 208 lists with 2408539 items (00.33% of keys, avg size 11579.51)0 hashs with 0 fields (00.00% of keys, avg size 0.00)62283 strings with 32642667 bytes (99.62% of keys, avg size 524.10)0 streams with 0 entries (00.00% of keys, avg size 0.00)31 sets with 1354 members (00.05% of keys, avg size 43.68)0 zsets with 0 members (00.00% of keys, avg size 0.00)从输出结果我们可以看到,每种数据类型所占用的最大长度或含有最多成员的 key 是哪一个,以及每种数据类型在整个实例中的占比和平均大小及成员数量。其实,使用这个命令的原理就是 Redis 在内部执行了 SCAN 命令,遍历整个实例中所有的 key,然后针对 key 的类型,分别执行 STRLEN、HLEN、LLEN、SCARD、ZCARD 命令,来获取 String 类型的长度、集合类型(Hash、List、Set、ZSet)的成员个数.注意,使用该--bigkeys进行大key的统计时要注意:
- 对于集合类型的Hash、List、Set、ZSet仅仅统计的是包含的成员个数,个数多并代表占用的内存大,仅仅是个参考;
- 对于高并发访问的集群,使用该命令会造成QPS增加,带来额外的性能开销,建议在业务低峰或者从节点进行扫描。
那针对 bigkey 导致延迟的问题,有什么好的解决方案呢?1)对大Key进行拆分如将一个含有数万成员的HASH Key拆分为多个HASH Key,并确保每个Key的成员数量在合理范围。特别是在RedisCluster架构下中,大Key的拆分对各节点间的内存平衡能够起到显著作用。2)优化使用删除Key的命令。Redis自4.0起提供了UNLINK命令,该命令可以替换DEL,能够以非阻塞的方式放到后台线程中缓慢逐步的清理大Key所占用的内存块,从而减轻了对Redis的影响;Redis 6.0 以上版本,建议开启 lazy-free 机制(配置参数:lazyfree-lazy-user-del = yes,6.2版本之后默认开启了),这样在 DEL删除大Key时,释放内存的动作也是在后台线程中执行的;3)尽量不写入大Key首先评估使用其他的存储形式,比如文档性数据库 MongoDB等;如果还无法避免使用BigKey,可以将大Key进行压缩后存储,并尽量根据业务精简Value的内容;建议单个Key的大小不要超过1K;4.不合理使用批处理命令网上有不少关于批量处理的一些优化,使用mget、mset代替多次的get、set等,减少网络IO开销以此提高redis的处理效率,特别是对于一些php短连接效果尤其明显。但是对于这些批量处理命令原生的mget、mset,非原生命令如pipeline,一定要注意控制单次批量操作的元素个数,否则会阻塞其它请求命令!建议控制在500以内。针对该种场景的优化方案:<ul>降低使用 O(N) 以上复杂度的命令,对于数据的计算聚合操作等可以适当的放在应用程序侧处理;
使用O(N) 复杂度的命令时,保证 N 尽量的小(推荐 N |