From 82ad75f33323c769eeb520f6a04c9a75e8167867 Mon Sep 17 00:00:00 2001
From: muqiuhan
举个例子:
-假设有两个数据库模型:User(用户)和 Post(帖子),一个用户可以有多篇帖子(一对多关系)。
现在,需要获取前 10 个用户以及他们各自的所有帖子。
-一种有问题的 ORM 实现(或不当的使用方式)可能会这样执行:
-SELECT * FROM User LIMIT 10; |
-- 用户 1 |
在这个场景下,总共执行了 1 + 10 = 11 次数据库查询。如果 N 的值很大(比如获取 1000 个用户),就会产生 1001 次查询,这对数据库造成巨大的、不必要的压力,并显著增加应用程序的响应时间。每一次数据库交互都有网络延迟和数据库处理的开销,N+1 次查询会将这些开销放大 N 倍。
-N+1 问题通常源于 ORM 处理关联数据的方式,特别是与“懒加载”(Lazy Loading)相关的策略。懒加载是指只有在显式访问关联属性时,ORM 才会去数据库加载这些数据。虽然这在某些情况下可以避免加载不需要的数据,但如果在循环中访问关联属性,就很容易触发 N+1 问题。
-然而,问题的根源在于没有有效地预先加载(或批量加载)所需的关联数据。即使不使用严格意义上的懒加载,如果 ORM 在处理关联查询时不够智能,采用了逐个获取关联对象的策略,同样会产生 N+1 查询。
-在 Prisma 出现之前或在其他 ORM 中,解决 N+1 问题常见的方法包括:
-JOIN 操作实现。例如,一次性查询出用户和他们的帖子。虽然这减少了查询次数,但复杂的 JOIN 可能会导致查询本身变得庞大和低效,并可能返回冗余数据。WHERE IN (...) 子句一次性加载所有相关的子对象。这种方式通常需要两次查询,但避免了 N 次单独的查询。Prisma ORM 在设计上就考虑了 N+1 问题,并提供了一种既方便开发者又高效的解决方案。当使用 Prisma Client 查询数据并需要包含关联模型时,Prisma 会自动优化查询,避免产生 N+1 查询,主要通过关系查询(Relation Queries)中的 include 选项或嵌套读取(nested reads)来实现这一点:
假设想获取所有用户及其发布的帖子,使用 Prisma Client,可以这样写:
-import { PrismaClient } from '@prisma/client' |
当执行上述查询时,Prisma 不会 生成 N+1 个 SQL 查询。而是首先会分析请求,并将其转化为数量非常有限的高效 SQL 查询。对于上面这个一对多关系的 include 查询,Prisma 通常会执行以下两步(类似于批量加载策略):
User 记录。SELECT "public"."User"."id", "public"."User"."name", /* ... other user fields */ FROM "public"."User" WHERE 1=1 |
id,通过 WHERE IN (...) 子句一次性查询所有相关的 Post 记录。SELECT "public"."Post"."id", "public"."Post"."title", "public"."Post"."authorId", /* ... other post fields */ FROM "public"."Post" WHERE "public"."Post"."authorId" IN ($1, $2, $3, ...) /* 这里的 $1, $2, ... 是第一步查到的用户 ID 列表 */ |
Prisma Client 在内存中将这两次查询的结果高效地组合起来,最终返回嵌套的、符合 TypeScript 类型的数据。
-举个例子:
+假设有两个数据库模型:User(用户)和 Post(帖子),一个用户可以有多篇帖子(一对多关系)。
现在,需要获取前 10 个用户以及他们各自的所有帖子。
+一种有问题的 ORM 实现(或不当的使用方式)可能会这样执行:
+SELECT * FROM User LIMIT 10; |
-- 用户 1 |
在这个场景下,总共执行了 1 + 10 = 11 次数据库查询。如果 N 的值很大(比如获取 1000 个用户),就会产生 1001 次查询,这对数据库造成巨大的、不必要的压力,并显著增加应用程序的响应时间。每一次数据库交互都有网络延迟和数据库处理的开销,N+1 次查询会将这些开销放大 N 倍。
+N+1 问题通常源于 ORM 处理关联数据的方式,特别是与“懒加载”(Lazy Loading)相关的策略。懒加载是指只有在显式访问关联属性时,ORM 才会去数据库加载这些数据。虽然这在某些情况下可以避免加载不需要的数据,但如果在循环中访问关联属性,就很容易触发 N+1 问题。
+然而,问题的根源在于没有有效地预先加载(或批量加载)所需的关联数据。即使不使用严格意义上的懒加载,如果 ORM 在处理关联查询时不够智能,采用了逐个获取关联对象的策略,同样会产生 N+1 查询。
+在 Prisma 出现之前或在其他 ORM 中,解决 N+1 问题常见的方法包括:
+JOIN 操作实现。例如,一次性查询出用户和他们的帖子。虽然这减少了查询次数,但复杂的 JOIN 可能会导致查询本身变得庞大和低效,并可能返回冗余数据。WHERE IN (...) 子句一次性加载所有相关的子对象。这种方式通常需要两次查询,但避免了 N 次单独的查询。Prisma ORM 在设计上就考虑了 N+1 问题,并提供了一种既方便开发者又高效的解决方案。当使用 Prisma Client 查询数据并需要包含关联模型时,Prisma 会自动优化查询,避免产生 N+1 查询,主要通过关系查询(Relation Queries)中的 include 选项或嵌套读取(nested reads)来实现这一点:
假设想获取所有用户及其发布的帖子,使用 Prisma Client,可以这样写:
+import { PrismaClient } from '@prisma/client' |
当执行上述查询时,Prisma 不会 生成 N+1 个 SQL 查询。而是首先会分析请求,并将其转化为数量非常有限的高效 SQL 查询。对于上面这个一对多关系的 include 查询,Prisma 通常会执行以下两步(类似于批量加载策略):
User 记录。SELECT "public"."User"."id", "public"."User"."name", /* ... other user fields */ FROM "public"."User" WHERE 1=1 |
id,通过 WHERE IN (...) 子句一次性查询所有相关的 Post 记录。SELECT "public"."Post"."id", "public"."Post"."title", "public"."Post"."authorId", /* ... other post fields */ FROM "public"."Post" WHERE "public"."Post"."authorId" IN ($1, $2, $3, ...) /* 这里的 $1, $2, ... 是第一步查到的用户 ID 列表 */ |
Prisma Client 在内存中将这两次查询的结果高效地组合起来,最终返回嵌套的、符合 TypeScript 类型的数据。
+在这个 PR 中支持了 Native AOT
+https://github.com/commandlineparser/commandline/pull/913
除此之外,还有一个更加精巧的 F# 库可以用,只有两百多行:
+https://github.com/B2R2-org/FsOptParse/
AOT 后的大小很可观,并且支持 full trim.
+用例:
+(** defines a state to pass to the option parser *) |
在这个 PR 中支持了 Native AOT
-https://github.com/commandlineparser/commandline/pull/913
除此之外,还有一个更加精巧的 F# 库可以用,只有两百多行:
-https://github.com/B2R2-org/FsOptParse/
AOT 后的大小很可观,并且支持 full trim.
-用例:
-(** defines a state to pass to the option parser *) |
从试验启动到结束,盲态管理需遵循标准化流程。在方案设计阶段,需明确盲法层级(如单盲、双盲或三盲),并制定相应的盲法实施计划。随机化编码由独立于临床团队的统计人员或第三方机构生成,治疗药物与对照品(如安慰剂)需在外观、气味、包装上完全一致,以确保盲态的完整性。在试验执行期间,发药、药物清点及受试者管理均由不参与疗效评估的人员负责,避免无意间泄露分组信息。当受试者出现严重不良事件,且必须知晓具体用药才能实施救治时,可启动紧急揭盲程序,但需严格记录揭盲原因、时间及操作人员,并在最终报告中说明。数据录入与管理环节,所有标识治疗组别的字段均需隐藏,数据库锁定前由盲态审核委员会进行一致性核查,确保无意外破盲。直至最终统计分析完成,研究团队方可正式揭盲。这一系列措施在《药物临床试验质量管理规范》及ICH-GCP指南中均有详细规定,旨在维护试验结果的科学可靠性[1]。
+盲态的核心价值在于控制两类主要偏倚:性能偏倚与检测偏倚。若研究者或受试者知晓治疗分配,可能在评估症状改善、记录不良事件或调整合并用药时产生倾向性,例如对试验组过度乐观或对对照组过度严苛,这种主观介入会扭曲药物真实疗效的测量。此外,在患者报告结局或生活质量评分等软终点指标中,知晓分组可能直接影响受试者的心理预期与反馈,进一步干扰结果有效性。通过盲态设计,可最大程度剥离人为因素对终点事件的干扰,从而更准确地估计药物与安慰剂或对照药之间的差异。从统计学角度,盲态有助于维持随机化带来的组间均衡性,使基线特征与未知混杂因素在组间均匀分布,提升因果推断的可靠性。历史上多项研究显示,非盲试验倾向于高估治疗效果约25%左右,尤其在主观性终点中偏倚更为显著[2]。
+参考文献
+International Council for Harmonisation of Technical Requirements for Pharmaceuticals for Human Use. ICH Harmonised Guideline: Integrated Addendum to ICH E6(R1): Guideline for Good Clinical Practice E6(R2). 2016. https://database.ich.org/sites/default/files/E6_R2_Addendum.pdf ↩︎
+Hróbjartsson A, et al. Blinded trials taken to the test: an analysis of randomized clinical trials that report tests for the success of blinding. International Journal of Epidemiology. 2007;36(3):654-663. https://doi.org/10.1093/ije/dym020 ↩︎
+从试验启动到结束,盲态管理需遵循标准化流程。在方案设计阶段,需明确盲法层级(如单盲、双盲或三盲),并制定相应的盲法实施计划。随机化编码由独立于临床团队的统计人员或第三方机构生成,治疗药物与对照品(如安慰剂)需在外观、气味、包装上完全一致,以确保盲态的完整性。在试验执行期间,发药、药物清点及受试者管理均由不参与疗效评估的人员负责,避免无意间泄露分组信息。当受试者出现严重不良事件,且必须知晓具体用药才能实施救治时,可启动紧急揭盲程序,但需严格记录揭盲原因、时间及操作人员,并在最终报告中说明。数据录入与管理环节,所有标识治疗组别的字段均需隐藏,数据库锁定前由盲态审核委员会进行一致性核查,确保无意外破盲。直至最终统计分析完成,研究团队方可正式揭盲。这一系列措施在《药物临床试验质量管理规范》及ICH-GCP指南中均有详细规定,旨在维护试验结果的科学可靠性[1]。
-盲态的核心价值在于控制两类主要偏倚:性能偏倚与检测偏倚。若研究者或受试者知晓治疗分配,可能在评估症状改善、记录不良事件或调整合并用药时产生倾向性,例如对试验组过度乐观或对对照组过度严苛,这种主观介入会扭曲药物真实疗效的测量。此外,在患者报告结局或生活质量评分等软终点指标中,知晓分组可能直接影响受试者的心理预期与反馈,进一步干扰结果有效性。通过盲态设计,可最大程度剥离人为因素对终点事件的干扰,从而更准确地估计药物与安慰剂或对照药之间的差异。从统计学角度,盲态有助于维持随机化带来的组间均衡性,使基线特征与未知混杂因素在组间均匀分布,提升因果推断的可靠性。历史上多项研究显示,非盲试验倾向于高估治疗效果约25%左右,尤其在主观性终点中偏倚更为显著[2]。
-参考文献
-International Council for Harmonisation of Technical Requirements for Pharmaceuticals for Human Use. ICH Harmonised Guideline: Integrated Addendum to ICH E6(R1): Guideline for Good Clinical Practice E6(R2). 2016. https://database.ich.org/sites/default/files/E6_R2_Addendum.pdf ↩︎
-Hróbjartsson A, et al. Blinded trials taken to the test: an analysis of randomized clinical trials that report tests for the success of blinding. International Journal of Epidemiology. 2007;36(3):654-663. https://doi.org/10.1093/ije/dym020 ↩︎
-直到他们在梦中期待新的一天中无限的未来。
可惜这都与我无关,那是我在学校的最后一天,我和他们一起放假,再也没回过学校。
但我当天的确睡了个好觉。
+]]> +希望我们再见时,可以向你讲述沿途风景,讲述我感受到的世界与爱。
也希望你在那边一切都好。
在星空的另一端,思念从未停止。
-]]> -我眯着眼看远处的灯火,
想那里面会不会有一盏,
是你为我留的。
-]]> -直到那些宏大的风景在眼里渐渐褪色,变成了一张张大同小异的明信片。我发现,自己并不渴望那个虚无缥缈的“远方”,我只是想找到一个可爱的地方,能让心跳安稳下来。
-于是,我回到了这座南方的小城。
-小城多雨,空气里总带着一股湿润的青苔味。
-我撑着一把黑色的旧伞,这把伞是我在离开这座城市的前两年就花十几元购入的,当时陈桉说我背着它像一个中二的少年。
-我走在交错纵横的弄堂里。这里的建筑保留着旧时代的局促与温情,电线杆上晾着洗得发白的床单,偶尔有几只野猫从瓦片上跳过。
-跨过那座被称为“通济”的石拱桥。桥下的河水浑浊而缓慢,像流淌不动的时光。我并没有明确的目的地,只是随性地穿梭在一条又一条弄堂之间。
-就在转过一个转角,走进一条地图上没有标注的、始料未及的小巷时,我停住了脚步。
-雨不知道什么时候停了。巷子很窄,两旁的红砖墙上爬满了枯萎的藤蔓。你就站在巷子的尽头,手里提着一袋刚出炉的生煎包,正低头躲避屋檐落下的水滴。
-在我的旅行生涯中,曾独自面对过一望无际的沙海,也曾在暴风雪的极夜徒步。我一直以为那些是勇敢。
-可直到这一刻,看着几步之外的你,我才意识到,自己一生中最勇敢的瞬间,竟然是此时此刻,是在这个平凡得不能再平凡的小巷里,决定不再逃避,决定停下流浪的脚步,去面对一个真实的、近在咫尺的人。
-你抬起头,看见了我。
-眼神里先是错愕,随即漾开了一层淡淡的、如同湖水般的温柔。
-“你回来了?”你轻声问,语气自然得就像我只是刚刚下楼买了一份报纸。
-我没有说话。我看着你,觉得你明明就在眼前,却又像从世界的尽头跋涉而来。
-或者说,有你在的地方,就是我所有流浪的终点。
-后来你说我的眼中藏着星点,那是久违的、对生活的热忱,你说我的嘴角不自觉地勾起一丝弧线,那是被治愈的证明。
-“我以为你还在地平线的那头。”你走近了几步,把那一小袋生煎包往怀里捂了捂,似乎怕冷风吹散了热气。
-“我找过了,地平线没有终点。”我接过你手中的伞,声音有些沙哑。
-这句话说出来很俗气,但在这一刻,在这一条被夕阳余晖染成橘色的小巷里,它是唯一的真理。
-我突然想把时间揉成碎片,捧在手心里。不想再去计算明天要去哪座城市,不想再去规划下一次航行。只想把这些碎片化的时间,全部挥霍在眼前的琐碎里:比如陪你走完这条巷子,比如和你一起吃掉这袋生煎,比如在下一个转角讨论晚饭的菜单。
-“还没吃晚饭吧?”你笑着问我,眼睛弯成了好看的月牙。
-“没呢。”
-“那……”你歪着头,眨了眨眼。
-我认真地看着你,郑重地点了点头。
-我知道,这次没有期限。在经历了无数个孤独的远方之后,在这一条始料未及的小巷里,再见面,就是永远。
]]>我依然是那个木讷的我。
海浪一遍遍慢慢地拍打着沙滩,没关系,慢一点也没关系。
在这片蓝色的寂静里听懂自己。
+]]> +直到那些宏大的风景在眼里渐渐褪色,变成了一张张大同小异的明信片。我发现,自己并不渴望那个虚无缥缈的“远方”,我只是想找到一个可爱的地方,能让心跳安稳下来。
+于是,我回到了这座南方的小城。
+小城多雨,空气里总带着一股湿润的青苔味。
+我撑着一把黑色的旧伞,这把伞是我在离开这座城市的前两年就花十几元购入的,当时陈桉说我背着它像一个中二的少年。
+我走在交错纵横的弄堂里。这里的建筑保留着旧时代的局促与温情,电线杆上晾着洗得发白的床单,偶尔有几只野猫从瓦片上跳过。
+跨过那座被称为“通济”的石拱桥。桥下的河水浑浊而缓慢,像流淌不动的时光。我并没有明确的目的地,只是随性地穿梭在一条又一条弄堂之间。
+就在转过一个转角,走进一条地图上没有标注的、始料未及的小巷时,我停住了脚步。
+雨不知道什么时候停了。巷子很窄,两旁的红砖墙上爬满了枯萎的藤蔓。你就站在巷子的尽头,手里提着一袋刚出炉的生煎包,正低头躲避屋檐落下的水滴。
+在我的旅行生涯中,曾独自面对过一望无际的沙海,也曾在暴风雪的极夜徒步。我一直以为那些是勇敢。
+可直到这一刻,看着几步之外的你,我才意识到,自己一生中最勇敢的瞬间,竟然是此时此刻,是在这个平凡得不能再平凡的小巷里,决定不再逃避,决定停下流浪的脚步,去面对一个真实的、近在咫尺的人。
+你抬起头,看见了我。
+眼神里先是错愕,随即漾开了一层淡淡的、如同湖水般的温柔。
+“你回来了?”你轻声问,语气自然得就像我只是刚刚下楼买了一份报纸。
+我没有说话。我看着你,觉得你明明就在眼前,却又像从世界的尽头跋涉而来。
+或者说,有你在的地方,就是我所有流浪的终点。
+后来你说我的眼中藏着星点,那是久违的、对生活的热忱,你说我的嘴角不自觉地勾起一丝弧线,那是被治愈的证明。
+“我以为你还在地平线的那头。”你走近了几步,把那一小袋生煎包往怀里捂了捂,似乎怕冷风吹散了热气。
+“我找过了,地平线没有终点。”我接过你手中的伞,声音有些沙哑。
+这句话说出来很俗气,但在这一刻,在这一条被夕阳余晖染成橘色的小巷里,它是唯一的真理。
+我突然想把时间揉成碎片,捧在手心里。不想再去计算明天要去哪座城市,不想再去规划下一次航行。只想把这些碎片化的时间,全部挥霍在眼前的琐碎里:比如陪你走完这条巷子,比如和你一起吃掉这袋生煎,比如在下一个转角讨论晚饭的菜单。
+“还没吃晚饭吧?”你笑着问我,眼睛弯成了好看的月牙。
+“没呢。”
+“那……”你歪着头,眨了眨眼。
+我认真地看着你,郑重地点了点头。
+我知道,这次没有期限。在经历了无数个孤独的远方之后,在这一条始料未及的小巷里,再见面,就是永远。
]]>+在有限认知下,持续对抗系统熵增,在相互冲突的目标间做出最优权衡,以可工程化的方式交付价值。
一、复杂性是固有属性,而非偶然缺陷
-- cgit v1.2.3