[{"data":1,"prerenderedAt":1593},["ShallowReactive",2],{"post:\u002Fposts\u002Fswdesign\u002Fsw-design-notes-19":3,"posts:ordered":1291},{"id":4,"title":5,"body":6,"category":1273,"cover":1274,"date":1275,"description":1276,"extension":1277,"featured":1278,"meta":1279,"navigation":1280,"path":1281,"seo":1282,"series":1283,"seriesOrder":1284,"stem":1285,"tags":1286,"__hash__":1290},"posts\u002Fposts\u002FswDesign\u002Fsw-design-notes-19.md","软件分析与设计笔记 19：规范化与逻辑数据模型",{"type":7,"value":8,"toc":1247},"minimark",[9,16,25,30,33,40,43,60,74,77,81,84,139,146,151,154,161,165,172,175,239,242,247,250,254,264,272,275,344,347,351,356,368,371,411,414,425,431,435,440,451,458,500,503,509,519,523,530,533,560,564,570,573,577,580,590,596,603,606,610,620,623,638,660,670,677,690,694,698,704,707,766,769,775,784,790,794,801,815,829,833,840,846,853,857,883,886,894,897,928,931,1009,1012],[10,11,12],"blockquote",{},[13,14,15],"p",{},"这是我在学习软件分析与设计时整理的系列笔记。概念部分是我按自己的理解写的，示例与图示都是自己编、自己画的。理解有偏差的地方欢迎指出。",[10,17,18],{},[13,19,20,24],{},[21,22,23],"strong",{},"贯穿案例","：片区家电维修派单系统。18 篇画了 ERD、写了数据字典，这一篇把表调到 3NF，再补成逻辑数据模型。",[26,27,29],"h2",{"id":28},"_0-一次改地址引发的麻烦","0 一次改地址引发的麻烦",[13,31,32],{},"客户张先生搬了家。客服在系统里改了客户档案上的地址。",[13,34,35,36,39],{},"三天后发现：他上个月的两张工单，工程师上门时还是按",[21,37,38],{},"旧地址","跑的。因为工单表里也存了一份客户地址 —— 当时为了「派单时看一眼方便」，一份地址被存了两处，改的时候只改了一处。",[13,41,42],{},"后果比听起来严重：",[44,45,46,50,53],"ul",{},[47,48,49],"li",{},"工程师白跑一趟（这就是 06 篇那个 11% 的缺料率之外，另一种「白跑」）；",[47,51,52],{},"客户投诉；",[47,54,55,56,59],{},"更麻烦的是",[21,57,58],{},"没人知道还有多少条旧地址散落在别的表里","。",[13,61,62,63,66,67,70,71],{},"这不是粗心，而是",[21,64,65],{},"数据冗余（data redundancy）"," 必然会带来的",[21,68,69],{},"更新异常（update anomaly）","：",[21,72,73],{},"同一份事实存在多处，就一定会有忘记同步的那一天。",[13,75,76],{},"规范化（normalization）要解决的就是这件事。",[26,78,80],{"id":79},"_1-三种异常","1 三种异常",[13,82,83],{},"冗余带来的问题一共三类：",[85,86,87,103],"table",{},[88,89,90],"thead",{},[91,92,93,97,100],"tr",{},[94,95,96],"th",{},"异常",[94,98,99],{},"表现",[94,101,102],{},"本例的具体情况",[104,105,106,117,128],"tbody",{},[91,107,108,111,114],{},[109,110,69],"td",{},[109,112,113],{},"改一处漏一处，数据自相矛盾",[109,115,116],{},"客户搬家后，工单上的旧地址没改",[91,118,119,122,125],{},[109,120,121],{},"插入异常（insert anomaly）",[109,123,124],{},"想录入某条信息，却因为没有别的主数据而录不进去",[109,126,127],{},"新配件要录入库存，但还没被任何工单用过，就无处可录",[91,129,130,133,136],{},[109,131,132],{},"删除异常（delete anomaly）",[109,134,135],{},"删掉一条记录，顺带把别的信息也弄丢了",[109,137,138],{},"删掉唯一一张用过配件 A231 的工单，A231 的库存信息也没了",[13,140,141,142,145],{},"第二行是插入异常的一个变体，值得细看：如果「配件信息」和「工单消耗」存在同一张表里，那么一个",[21,143,144],{},"刚采购回来、还没被用过的配件","就没有地方录入 —— 因为它没有对应的工单。",[13,147,148],{},[21,149,150],{},"「想存 A，但必须先有 B」这种别扭，就是插入异常的信号。",[13,152,153],{},"第三行则说明了删除异常的代价：一张工单被删，带的不是「一条记录」，而是「某个配件的全部存在证明」。",[13,155,156],{},[157,158],"img",{"alt":159,"src":160},"规范化四个阶段各自解决的问题：未规范化存在重复组与重复数据，第一范式拆掉重复组，第二范式消除只依赖部分主键的字段，第三范式消除依赖其他非主键字段的字段；底部说明派生字段不该存储、优化是另一回事","\u002Fimages\u002Fsw-design-notes-19-normalization.svg",[26,162,164],{"id":163},"_2-unf-到-3nf每一步拿走一种重复","2 UNF 到 3NF：每一步拿走一种重复",[13,166,167,168,171],{},"规范化的做法是",[21,169,170],{},"对表逐条套用规则","，每套用一次就上一个范式。",[13,173,174],{},"本例的起点是这样一张宽表（UNF）：",[85,176,177,199],{},[88,178,179],{},[91,180,181,184,187,190,193,196],{},[94,182,183],{},"工单号",[94,185,186],{},"客户名",[94,188,189],{},"客户地址",[94,191,192],{},"工程师名",[94,194,195],{},"配件（多行）",[94,197,198],{},"合计金额",[104,200,201,221],{},[91,202,203,206,209,212,215,218],{},[109,204,205],{},"WO-001",[109,207,208],{},"张先生",[109,210,211],{},"海淀 xx 小区",[109,213,214],{},"老陈",[109,216,217],{},"A231 × 2；B110 × 1",[109,219,220],{},"560",[91,222,223,226,228,230,233,236],{},[109,224,225],{},"WO-002",[109,227,208],{},[109,229,211],{},[109,231,232],{},"阿明",[109,234,235],{},"C305 × 1",[109,237,238],{},"180",[13,240,241],{},"这张表把五类信息挤在了一起：工单、客户、工程师、配件明细、金额。下面四步逐步拆开它。",[243,244,246],"h3",{"id":245},"_21-未规范化形式unnormalized-form-unf","2.1 未规范化形式（unnormalized form, UNF）",[13,248,249],{},"原始的宽表：一张表里既有工单信息，又有客户信息，还有多行配件明细挤在同一格里。",[243,251,253],{"id":252},"_22-第一范式first-normal-form-1nf","2.2 第一范式（first normal form, 1NF）",[13,255,256,257,260,261,70],{},"消除",[21,258,259],{},"多值属性（multivalue attribute）"," 与",[21,262,263],{},"重复组（repeating group）",[44,265,266,269],{},[47,267,268],{},"每个单元格只放一个值（原子值，atomic value）；",[47,270,271],{},"把重复出现的配件行拆成独立记录，而不是塞在一格里。",[13,273,274],{},"拆完是这样（主键变成「工单号 + 配件号」）：",[85,276,277,295],{},[88,278,279],{},[91,280,281,283,286,289,291,293],{},[94,282,183],{},[94,284,285],{},"配件号",[94,287,288],{},"数量",[94,290,186],{},[94,292,189],{},[94,294,192],{},[104,296,297,313,329],{},[91,298,299,301,304,307,309,311],{},[109,300,205],{},[109,302,303],{},"A231",[109,305,306],{},"2",[109,308,208],{},[109,310,211],{},[109,312,214],{},[91,314,315,317,320,323,325,327],{},[109,316,205],{},[109,318,319],{},"B110",[109,321,322],{},"1",[109,324,208],{},[109,326,211],{},[109,328,214],{},[91,330,331,333,336,338,340,342],{},[109,332,225],{},[109,334,335],{},"C305",[109,337,322],{},[109,339,208],{},[109,341,211],{},[109,343,232],{},[13,345,346],{},"1NF 之后，重复的问题只是被摊开了，还没有解决 —— 注意客户名和地址现在重复了三次。",[243,348,350],{"id":349},"_23-第二范式second-normal-form-2nf","2.3 第二范式（second normal form, 2NF）",[13,352,256,353,70],{},[21,354,355],{},"部分依赖（partial dependency）",[44,357,358,361],{},[47,359,360],{},"前提是这个表有复合主键；",[47,362,363,364,367],{},"规则是：",[21,365,366],{},"非主键字段必须依赖整个主键","，不能只依赖其中一部分。",[13,369,370],{},"上面那张表的主键是「工单号 + 配件号」。逐字段检查它依赖谁：",[85,372,373,386],{},[88,374,375],{},[91,376,377,380,383],{},[94,378,379],{},"字段",[94,381,382],{},"依赖",[94,384,385],{},"判断",[104,387,388,398],{},[91,389,390,392,395],{},[109,391,288],{},[109,393,394],{},"工单号 + 配件号",[109,396,397],{},"✅ 完整依赖，留下",[91,399,400,403,408],{},[109,401,402],{},"客户名 \u002F 客户地址 \u002F 工程师名",[109,404,405],{},[21,406,407],{},"只依赖工单号",[109,409,410],{},"❌ 部分依赖，要搬走",[13,412,413],{},"搬走之后分成两张表：",[415,416,421],"pre",{"className":417,"code":419,"language":420},[418],"language-text","work_order_part(work_order_no, part_no, qty)     -- 主键：工单号 + 配件号\nwork_order(work_order_no, customer_name, customer_address, engineer_name)\n","text",[422,423,419],"code",{"__ignoreMap":424},"",[13,426,427,430],{},[21,428,429],{},"注意这一步的判据非常机械：看每个字段能不能只靠主键的一半确定","。能，就是部分依赖。",[243,432,434],{"id":433},"_24-第三范式third-normal-form-3nf","2.4 第三范式（third normal form, 3NF）",[13,436,256,437,70],{},[21,438,439],{},"传递依赖（transitive dependency）",[44,441,442,448],{},[47,443,363,444,447],{},[21,445,446],{},"非主键字段不能依赖另一个非主键字段","；",[47,449,450],{},"典型例子：工单表里有「客户号」和「客户地址」。地址依赖的是客户号（一个非主键字段），而不是工单号 —— 所以地址该搬到客户表。",[13,452,453,454,457],{},"本例里 ",[422,455,456],{},"work_order"," 表还有两个传递依赖：",[85,459,460,472],{},[88,461,462],{},[91,463,464,466,469],{},[94,465,379],{},[94,467,468],{},"依赖链",[94,470,471],{},"处理",[104,473,474,487],{},[91,475,476,481,484],{},[109,477,478],{},[422,479,480],{},"customer_address",[109,482,483],{},"工单号 → 客户名 → 客户地址",[109,485,486],{},"搬到 customer 表",[91,488,489,494,497],{},[109,490,491],{},[422,492,493],{},"engineer_name",[109,495,496],{},"工单号 → 工程师编号 → 工程师名",[109,498,499],{},"搬到 engineer 表",[13,501,502],{},"拆完得到四张表：",[415,504,507],{"className":505,"code":506,"language":420},[418],"customer(customer_id, name, phone, address)\nengineer(engineer_id, name, phone, skills)\nwork_order(work_order_no, customer_id, engineer_id, status, created_at)\nwork_order_part(work_order_no, part_no, qty)\n",[422,508,506],{"__ignoreMap":424},[13,510,511,518],{},[21,512,513,514,517],{},"到这一步，「客户搬家」只需要改 ",[422,515,516],{},"customer"," 表的一行","。更新异常的根源被消除了。",[243,520,522],{"id":521},"_25-函数依赖三条规则的统一说法","2.5 函数依赖：三条规则的统一说法",[13,524,525,526,529],{},"函数依赖（functional dependency）是这三条规则背后的统一说法：",[21,527,528],{},"如果知道 A 就能唯一确定 B，就写成 A → B","。规范化要做的，是让所有非主键字段都直接依赖于主键。",[13,531,532],{},"三个范式其实是同一句话的三次检查：",[44,534,535,541,551],{},[47,536,537,540],{},[21,538,539],{},"1NF"," —— 消除「一个格子里有多个值」这种非函数依赖；",[47,542,543,546,547,550],{},[21,544,545],{},"2NF"," —— 消除 ",[422,548,549],{},"配件号 → 数量","（只依赖主键一半）这类依赖；",[47,552,553,546,556,559],{},[21,554,555],{},"3NF",[422,557,558],{},"客户号 → 地址","（非主键依赖非主键）这类依赖。",[243,561,563],{"id":562},"_26-一个能背下来的检查法","2.6 一个能背下来的检查法",[13,565,566,567],{},"规范化到最后，可以用一句话自检：",[21,568,569],{},"每个非主键字段，都必须满足「只依赖主键、依赖整个主键、且只依赖主键」。",[13,571,572],{},"这三句正好对应三个范式，而且判断起来很快 —— 对着每张表的每个字段问一遍即可。",[26,574,576],{"id":575},"_3-派生字段derived-attribute不存","3 派生字段（derived attribute）不存",[13,578,579],{},"工单明细里有「单价」和「数量」，那么「小计」要不要存？「合计金额」呢？",[13,581,582,585,586,589],{},[21,583,584],{},"都不要。"," 因为小计可以由另外两个字段算出来，合计又由所有小计加起来 —— 这类字段叫",[21,587,588],{},"派生字段（derived attribute）","。存它的代价是：每次单价或数量变化，你都必须记得同步更新它；漏一次，数据永久不一致。",[13,591,592,593],{},"回到 UNF 那张宽表里的「合计金额」：它其实有三个来源可能变（单价改、数量改、明细增删）。",[21,594,595],{},"每多一个来源，就多一条漏更新的路径。",[13,597,598,599,602],{},"正确做法是",[21,600,601],{},"在需要的地方算","（查询时计算、或由视图\u002F应用层计算）。如果确实因为性能原因要存，那要配套约定清楚「谁负责更新、什么时候更新」。",[13,604,605],{},"这条规则和笔记 09 讲业务实体时说的「能算出来的不要存」是同一条，也和 18 篇数据字典里「缺料率不建字段」是同一个道理。",[26,607,609],{"id":608},"_4-优化把表合回去的两种情形","4 优化：把表合回去的两种情形",[13,611,612,613,616,617,59],{},"规范化不是终点。有些场景下，把表适度合回去反而更好 —— 这叫",[21,614,615],{},"优化（optimization）","，它和规范化是",[21,618,619],{},"方向相反的两种权衡",[13,621,622],{},"常见的两种情形：",[624,625,626,632],"ol",{},[47,627,628,631],{},[21,629,630],{},"频繁关联查询的小表。"," 两张表总是一起查、且都很少变动，合并可以减少连接；",[47,633,634,637],{},[21,635,636],{},"只在一处使用的数据。"," 某个字段只在同一个业务流程里被读写，且没有独立生命周期。",[13,639,640,641,643,644,647,648,651,652,655,656,659],{},"本例里有一个合适的候选：",[422,642,456],{}," 与它的 ",[422,645,646],{},"work_order_part"," ",[21,649,650],{},"不该合并","（因为有独立生命周期），但 ",[422,653,654],{},"stock_item"," 与 ",[422,657,658],{},"part","（配件主数据）可以考虑合并 —— 如果仓管系统和派单系统是同一个，配件名称、规格这些字段就不必单独一张表。",[13,661,662,663,647,666,669],{},"但 ",[422,664,665],{},"customer.address",[21,667,668],{},"绝对不能合回去","。这正是本篇开头那个事故。",[13,671,672,673,676],{},"关键是把话说清楚：",[21,674,675],{},"这不是「没做规范化」，而是权衡后退一步","。判断依据仍然是那条：这份数据会不会因为存在两处而不一致？如果合起来不会引入冗余，那合起来就是合理的。",[13,678,679,682,683,686,687,689],{},[21,680,681],{},"换句话说：可以为了性能容忍冗余，但必须知道自己在容忍什么","。本例的「容忍清单」应该写在设计文档里，注明「",[422,684,685],{},"stock_item.part_name"," 冗余自 ",[422,688,658],{},"，由采购入库时同步」。",[26,691,693],{"id":692},"_5-逻辑数据模型ldm","5 逻辑数据模型（LDM）",[243,695,697],{"id":696},"_51-它和-erd-的关系","5.1 它和 ERD 的关系",[13,699,700,701,59],{},"完成规范化之后，把所有表（这里叫 3NF 表）之间的关系补出来，就得到",[21,702,703],{},"逻辑数据模型（logical data model, LDM）",[13,705,706],{},"它和 ERD 的关系可以这样理解：",[85,708,709,721],{},[88,710,711],{},[91,712,713,715,718],{},[94,714],{},[94,716,717],{},"ERD",[94,719,720],{},"逻辑数据模型（LDM）",[104,722,723,734,745,756],{},[91,724,725,728,731],{},[109,726,727],{},"关注",[109,729,730],{},"业务概念与关系",[109,732,733],{},"具体的表、字段、键与关系",[91,735,736,739,742],{},[109,737,738],{},"粒度",[109,740,741],{},"概念级",[109,743,744],{},"接近实现级，但仍不绑定具体产品",[91,746,747,750,753],{},[109,748,749],{},"是否含类型长度",[109,751,752],{},"通常不含",[109,754,755],{},"含",[91,757,758,761,764],{},[109,759,760],{},"是否含外键",[109,762,763],{},"不含",[109,765,755],{},[13,767,768],{},"拿 18 篇那份数据字典填进去，LDM 就是这个样子：",[415,770,773],{"className":771,"code":772,"language":420},[418],"customer(customer_id PK, name, phone, address)\nengineer(engineer_id PK, name, phone, skills)\ndistrict(district_id PK, name, manager_id)\nengineer_district(engineer_id PK, district_id PK)          ← 多对多拆出的关联表\nwork_order(order_no PK, customer_id FK, engineer_id FK,\n           district_id FK, appliance_type, fault_desc, status, created_at, closed_at)\nstock_item(part_no PK, name, on_hand_qty, safe_qty, district_id FK)\npart_usage(work_order_id PK FK, part_no PK FK, qty)\n",[422,774,772],{"__ignoreMap":424},[13,776,777,783],{},[21,778,779,780],{},"注意第五行 ",[422,781,782],{},"engineer_district"," —— 它就是 18 篇里那个「一个工程师能属多个片区吗」问出来的结果。这一个问题让表从 6 张变成 7 张。",[13,785,786,787],{},"LDM 本身已经是一个关系模型（relational model）：表、字段、键、关系都明确了。再往后才是物理设计 —— 选具体的关系型数据库（relational database）、定索引、考虑分区。",[21,788,789],{},"LDM 阶段还在讲「逻辑」，还没到「用哪个产品、建什么索引」。",[243,791,793],{"id":792},"_52-参照完整性","5.2 参照完整性",[13,795,796,797,800],{},"LDM 里有个概念要提前留意 —— ",[21,798,799],{},"参照完整性（referential integrity）","：外键指向的主键必须存在。它保证了「配件消耗的父工单一定在」。",[13,802,803,804,814],{},"本例里参照完整性有个值得单独说的细节：",[21,805,806,809,810,813],{},[422,807,808],{},"part_usage.part_no"," 指向 ",[422,811,812],{},"stock_item.part_no","，但配件可能被停用","。如果直接拒绝删除停用配件，历史工单就会出问题。",[13,816,817,818,821,822,825,826],{},"处理办法不是关掉参照完整性，而是",[21,819,820],{},"加一个「停用」状态","（",[422,823,824],{},"stock_item.is_active","），让配件永远存在、只是不能被新申请引用。",[21,827,828],{},"这又是一个「技术约束要靠业务方案解决」的例子。",[243,830,832],{"id":831},"_53-规范化做完还剩什么没解决","5.3 规范化做完，还剩什么没解决",[13,834,835,836,839],{},"值得停下来想一下：3NF 解决了冗余，但它",[21,837,838],{},"不解决性能","。本例里 06 篇那张交易量表说过，「客户查进度」一天 3600 次，而这个查询要 join 三张表（工单 + 客户 + 工程师）。",[13,841,842,845],{},[21,843,844],{},"规范化做得越彻底，join 越多","。这正是 §4 那个「方向相反的权衡」存在的原因 —— 也是为什么物理设计阶段还要做索引、物化视图、读库分离。",[13,847,848,849,852],{},"一句话：",[21,850,851],{},"3NF 是正确性的保证，不是性能的保证","。两者要分开考虑。",[26,854,856],{"id":855},"_6-常见误区","6 常见误区",[44,858,859,865,871,877],{},[47,860,861,864],{},[21,862,863],{},"以为规范化就是「表越多越好」。"," 规范化的目标是消除冗余，不是增加表数。过度拆表会带来大量连接；",[47,866,867,870],{},[21,868,869],{},"把派生字段当成普通字段存。"," 见上面第 3 节；",[47,872,873,876],{},[21,874,875],{},"跳过 1NF 直接讨论 3NF。"," 重复组没拆掉的话，后面几步没有意义；",[47,878,879,882],{},[21,880,881],{},"把 LDM 当成建表脚本。"," LDM 还是逻辑模型，字段名可以更贴近业务。",[13,884,885],{},"再加一条本例里特别容易犯的：",[44,887,888],{},[47,889,890,893],{},[21,891,892],{},"以为 3NF 之后就没事了。"," 3NF 只管冗余，不管性能、不管并发、不管参照完整性的边界情况（见 §5.2）。",[26,895,896],{"id":896},"本节自测",[624,898,899,902,905,908,911,914,922,925],{},[47,900,901],{},"「想录入一个新配件，却因为没有工单用过而录不进去」属于哪一类异常？",[47,903,904],{},"1NF、2NF、3NF 分别消除的是什么？各自的前提条件是什么？",[47,906,907],{},"工单明细表里的「小计」为什么不该存储？如果非要存，需要补什么约定？",[47,909,910],{},"什么情况下把两张表合回去是合理的？",[47,912,913],{},"LDM 和 ERD 的差别在哪？LDM 之后还差哪一步？",[47,915,453,916,918,919,921],{},[422,917,456],{}," 表的 ",[422,920,480],{}," 属于哪种依赖？为什么该搬到 customer 表？",[47,923,924],{},"用了 3NF 之后，为什么查询反而可能变慢？",[47,926,927],{},"配件被停用时，参照完整性怎么处理才不影响历史工单？",[243,929,930],{"id":930},"参考答案",[624,932,933,938,952,959,970,984,993,999],{},[47,934,935,937],{},[21,936,121],{},"。特征是「想存 A，但必须先有 B」—— 新配件没被任何工单用过，就没有地方录入（§1）。",[47,939,940,943,944,947,948,951],{},[21,941,942],{},"1NF 消除多值属性与重复组","（前提：一格一值）；",[21,945,946],{},"2NF 消除部分依赖","（前提：表有复合主键）；",[21,949,950],{},"3NF 消除传递依赖","（规则：非主键字段不能依赖另一个非主键字段）（§2.2–2.4）。",[47,953,954,955,958],{},"因为它是",[21,956,957],{},"派生字段","（单价 × 数量），存了就要在每次单价或数量变化时同步更新，漏一次就永久不一致。非要存必须配套约定「谁负责更新、什么时候更新」（§3）。",[47,960,961,962,965,966,969],{},"①",[21,963,964],{},"频繁关联查询的小表","（总是一起查、很少变动）；②",[21,967,968],{},"只在一处使用、没有独立生命周期的数据","。判据：合起来会不会引入冗余（§4）。",[47,971,972,975,976,979,980,983],{},[21,973,974],{},"ERD 是概念级","（业务概念与关系，不含类型长度与外键）；",[21,977,978],{},"LDM 接近实现级","（含字段、键、外键、类型长度），但仍不绑定具体产品。LDM 之后还差",[21,981,982],{},"物理设计","（选数据库、定索引、分区）（§5.1）。",[47,985,986,70,989,992],{},[21,987,988],{},"传递依赖",[422,990,991],{},"工单号 → 客户号 → 客户地址","。地址依赖的是非主键字段「客户号」，所以该搬到 customer 表，否则客户搬家时工单上的地址不会跟着改（§2.4）。",[47,994,995,996,998],{},"因为",[21,997,844],{},"。本例「查进度」要 join 三张表，而它一天被查 3600 次。3NF 保证正确性，不保证性能（§5.3）。",[47,1000,1001,1002,821,1005,1008],{},"不要关掉参照完整性，而是",[21,1003,1004],{},"给配件加「停用」状态",[422,1006,1007],{},"is_active","）—— 配件永远存在、只是不能被新申请引用。技术约束要靠业务方案解决（§5.2）。",[26,1010,1011],{"id":1011},"术语表",[85,1013,1014,1027],{},[88,1015,1016],{},[91,1017,1018,1021,1024],{},[94,1019,1020],{},"术语",[94,1022,1023],{},"中文",[94,1025,1026],{},"一句话解释",[104,1028,1029,1040,1051,1062,1073,1084,1095,1106,1117,1128,1139,1150,1161,1172,1182,1193,1203,1214,1225,1236],{},[91,1030,1031,1034,1037],{},[109,1032,1033],{},"normalization",[109,1035,1036],{},"规范化",[109,1038,1039],{},"按规则调整表结构以消除冗余",[91,1041,1042,1045,1048],{},[109,1043,1044],{},"data redundancy",[109,1046,1047],{},"数据冗余",[109,1049,1050],{},"同一份事实被存储在多个地方",[91,1052,1053,1056,1059],{},[109,1054,1055],{},"update anomaly",[109,1057,1058],{},"更新异常",[109,1060,1061],{},"改一处漏一处导致数据互相矛盾",[91,1063,1064,1067,1070],{},[109,1065,1066],{},"insert anomaly",[109,1068,1069],{},"插入异常",[109,1071,1072],{},"想录入某条信息却因缺少别的主数据而无法录入",[91,1074,1075,1078,1081],{},[109,1076,1077],{},"delete anomaly",[109,1079,1080],{},"删除异常",[109,1082,1083],{},"删一条记录顺带丢失了别的信息",[91,1085,1086,1089,1092],{},[109,1087,1088],{},"multivalue attribute",[109,1090,1091],{},"多值属性",[109,1093,1094],{},"一个单元格里塞了多个值",[91,1096,1097,1100,1103],{},[109,1098,1099],{},"repeating group",[109,1101,1102],{},"重复组",[109,1104,1105],{},"同一组字段在同一记录里重复出现",[91,1107,1108,1111,1114],{},[109,1109,1110],{},"unnormalized form（UNF）",[109,1112,1113],{},"未规范化形式",[109,1115,1116],{},"尚未套用任何规范化规则的原始表",[91,1118,1119,1122,1125],{},[109,1120,1121],{},"first normal form（1NF）",[109,1123,1124],{},"第一范式",[109,1126,1127],{},"每格只放一个原子值，消除重复组",[91,1129,1130,1133,1136],{},[109,1131,1132],{},"second normal form（2NF）",[109,1134,1135],{},"第二范式",[109,1137,1138],{},"非主键字段依赖整个主键，消除部分依赖",[91,1140,1141,1144,1147],{},[109,1142,1143],{},"third normal form（3NF）",[109,1145,1146],{},"第三范式",[109,1148,1149],{},"非主键字段不依赖其他非主键字段",[91,1151,1152,1155,1158],{},[109,1153,1154],{},"functional dependency",[109,1156,1157],{},"函数依赖",[109,1159,1160],{},"知道 A 就能唯一确定 B 的关系",[91,1162,1163,1166,1169],{},[109,1164,1165],{},"partial dependency",[109,1167,1168],{},"部分依赖",[109,1170,1171],{},"只依赖复合主键中的一部分",[91,1173,1174,1177,1179],{},[109,1175,1176],{},"transitive dependency",[109,1178,988],{},[109,1180,1181],{},"依赖了另一个非主键字段",[91,1183,1184,1187,1190],{},[109,1185,1186],{},"atomic value",[109,1188,1189],{},"原子值",[109,1191,1192],{},"不可再分的最小数据单位",[91,1194,1195,1198,1200],{},[109,1196,1197],{},"derived attribute",[109,1199,957],{},[109,1201,1202],{},"可以由其他字段计算出来的字段",[91,1204,1205,1208,1211],{},[109,1206,1207],{},"optimization",[109,1209,1210],{},"优化",[109,1212,1213],{},"出于性能考虑对规范化结果做的调整",[91,1215,1216,1219,1222],{},[109,1217,1218],{},"logical data model（LDM）",[109,1220,1221],{},"逻辑数据模型",[109,1223,1224],{},"3NF 之上补齐字段、键与关系的模型",[91,1226,1227,1230,1233],{},[109,1228,1229],{},"relational model",[109,1231,1232],{},"关系模型",[109,1234,1235],{},"以表与关系组织数据的模型",[91,1237,1238,1241,1244],{},[109,1239,1240],{},"referential integrity",[109,1242,1243],{},"参照完整性",[109,1245,1246],{},"外键指向的主键必须存在",{"title":424,"searchDepth":1248,"depth":1248,"links":1249},3,[1250,1252,1253,1261,1262,1263,1268,1269,1272],{"id":28,"depth":1251,"text":29},2,{"id":79,"depth":1251,"text":80},{"id":163,"depth":1251,"text":164,"children":1254},[1255,1256,1257,1258,1259,1260],{"id":245,"depth":1248,"text":246},{"id":252,"depth":1248,"text":253},{"id":349,"depth":1248,"text":350},{"id":433,"depth":1248,"text":434},{"id":521,"depth":1248,"text":522},{"id":562,"depth":1248,"text":563},{"id":575,"depth":1251,"text":576},{"id":608,"depth":1251,"text":609},{"id":692,"depth":1251,"text":693,"children":1264},[1265,1266,1267],{"id":696,"depth":1248,"text":697},{"id":792,"depth":1248,"text":793},{"id":831,"depth":1248,"text":832},{"id":855,"depth":1251,"text":856},{"id":896,"depth":1251,"text":896,"children":1270},[1271],{"id":930,"depth":1248,"text":930},{"id":1011,"depth":1251,"text":1011},"学习",null,"2026-11-10","一次改地址引发的数据不一致，讲清规范化的真正动机；UNF 到 3NF 每一步在解决什么问题，派生字段为什么不能存，优化为什么和规范化方向相反，以及逻辑数据模型在 3NF 之上补了什么。","md",false,{},true,"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-19",{"title":5,"description":1276},"软件分析与设计笔记",19,"posts\u002FswDesign\u002Fsw-design-notes-19",[1287,1036,1288,1289],"数据库设计","数据建模","学习笔记","78CaRcyWq0fhR6yDYAE8hpAcAgqRlDe54uiPLnIFk_k",[1292,1298,1304,1310,1316,1322,1328,1334,1340,1346,1347,1353,1359,1365,1371,1377,1383,1389,1395,1401,1407,1413,1419,1425,1431,1437,1442,1447,1453,1459,1463,1467,1471,1475,1479,1483,1487,1491,1495,1499,1503,1509,1513,1519,1523,1527,1531,1535,1539,1543,1547,1552,1556,1560,1565,1570,1576,1580,1585,1589],{"path":1293,"title":1294,"description":1295,"date":1296,"series":1283,"seriesOrder":1297},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-28","软件分析与设计笔记 28：draw.io 作图手册：五张图的符号位置、落笔顺序与自检","不模拟软件界面，只讲清三件事：每个 UML 符号在 draw.io 里搜什么词、一张图该按什么顺序落笔、画完拿什么标准自查。覆盖用例图、活动图、顺序图、类图、界面草图五种图纸，末尾附「最容易画反的十二笔」总检表。","2026-11-19",28,{"path":1299,"title":1300,"description":1301,"date":1302,"series":1283,"seriesOrder":1303},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-27","软件分析与设计笔记 27：Paper 1 应试：从读案例到交卷的一次完整演算","拿一份完整的案例，从头到尾走一遍：怎么读案例列角色、怎么在四个小问之间分配时间、每一张图从哪一句话推出来、两张表怎么填、交卷前拿什么清单自查。附 C25 真题与样卷的题型分值对照。","2026-11-18",27,{"path":1305,"title":1306,"description":1307,"date":1308,"series":1283,"seriesOrder":1309},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-26","软件分析与设计笔记 26：总结：把整条链路串起来","系列收尾：从需求到代码的完整链路回顾、每一环的产出与验收方式、六条贯穿全系列的原则，以及一份可以直接拿去做项目自查的清单。","2026-11-17",26,{"path":1311,"title":1312,"description":1313,"date":1314,"series":1283,"seriesOrder":1315},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-25","软件分析与设计笔记 25：代码重构：坏味道与手法","重构不改变外部行为、只改善内部结构：四种常见坏味道与对应手法、为什么动手之前必须先有测试、什么时候该重构，以及重构和前面那些设计模型是什么关系。","2026-11-16",25,{"path":1317,"title":1318,"description":1319,"date":1320,"series":1283,"seriesOrder":1321},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-24","软件分析与设计笔记 24：设计原则：内聚、耦合与多态","好设计怎么衡量：内聚与耦合分别在说什么、一次从「差设计」到「多态重设计」的完整对照、抽象类与接口的选择依据，以及复用为什么会从优势变成风险。","2026-11-15",24,{"path":1323,"title":1324,"description":1325,"date":1326,"series":1283,"seriesOrder":1327},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-23","软件分析与设计笔记 23：设计建模：从对象模型到能实现的方案","分析模型能用但还不能直接开工：对象模型怎么映射到关系模型、继承映射的三种方案怎么选、数据访问层与 Data Broker 解决什么问题，以及三层架构与 MVC 的分工。","2026-11-14",23,{"path":1329,"title":1330,"description":1331,"date":1332,"series":1283,"seriesOrder":1333},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-22","软件分析与设计笔记 22：分析建模：边界、实体与控制对象","不经过分析直接写代码会踩什么坑、三种分析对象各管一段、怎么用顺序图完成一次用例实现，以及从顺序图导出类的属性、操作与关系的方法。","2026-11-13",22,{"path":1335,"title":1336,"description":1337,"date":1338,"series":1283,"seriesOrder":1339},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-21","软件分析与设计笔记 21：UML：类图与顺序图","为什么需要一种共同的画法：类符号三格怎么画、五种关系的符号与设计含义、每种关系落到 Java 是什么写法，顺序图的三要素怎么摆；以及这三种最容易找不到的符号在 draw.io 里各搜什么词。","2026-11-12",21,{"path":1341,"title":1342,"description":1343,"date":1344,"series":1283,"seriesOrder":1345},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-20","软件分析与设计笔记 20：面向对象概念：对象、关系与多态","面向对象不是「用某个语言写代码」：对象与类的区别、封装真正在保护什么、五种关系（依赖、关联、聚合、组合、继承）各自何时用，以及多态为什么能减少条件判断。","2026-11-11",20,{"path":1281,"title":5,"description":1276,"date":1275,"series":1283,"seriesOrder":1284},{"path":1348,"title":1349,"description":1350,"date":1351,"series":1283,"seriesOrder":1352},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-18","软件分析与设计笔记 18：数据库分析：实体、关系与数据字典","为什么不能拿到需求就动手建表：实体与关系怎么判定、基数为什么必须由业务方确认、数据命名要遵守什么规则，以及数据字典四列里最容易被空着的那一列。","2026-11-09",18,{"path":1354,"title":1355,"description":1356,"date":1357,"series":1283,"seriesOrder":1358},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-17","软件分析与设计笔记 17：用生成式 AI 做设计与开发：能用的场景与要防的坑","生成式 AI 在工作流里真正站得住的三个场景 —— 造示例数据、解释陌生代码、起草初稿；它为什么会复现社会偏见、为什么会编得像真的，以及哪些内容绝不能直接采用。","2026-11-08",17,{"path":1360,"title":1361,"description":1362,"date":1363,"series":1283,"seriesOrder":1364},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-16","软件分析与设计笔记 16：从 persona 到 URS：一份交付物的完整骨架","把前面十几篇串成一份能交出去的东西：用户需求规格说明书的九个部分、每一章来自哪个环节、由谁确认，以及面向决策者的提案该讲清哪三件事。","2026-11-07",16,{"path":1366,"title":1367,"description":1368,"date":1369,"series":1283,"seriesOrder":1370},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-15","软件分析与设计笔记 15：非功能需求：把「快、稳、安全」变成数字","为什么「系统要快」不是一条需求：非功能需求怎么量化，决定性能的用户量、交易量、数据量三张表怎么估，安全需求要写清什么，以及可靠性的「不可用」该怎么定义。","2026-11-06",15,{"path":1372,"title":1373,"description":1374,"date":1375,"series":1283,"seriesOrder":1376},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-14","软件分析与设计笔记 14：软件测试：层级、方法与等价类划分","「我们测过了」这句话问题在哪：为什么静态评审和动态测试缺一不可，五个测试层级各自验证什么，白盒与黑盒的差别，以及用等价类划分和边界值把无限输入变成有限用例的方法。","2026-11-05",14,{"path":1378,"title":1379,"description":1380,"date":1381,"series":1283,"seriesOrder":1382},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-13","软件分析与设计笔记 13：需求获取：五种调查方法与一场访谈","需求从哪里来：五种事实调查方法各自适合回答什么、代价是什么，访谈从准备到记录的全过程，问卷设计里最容易犯的错，以及访谈记录必须写下来的那几项信息。","2026-11-04",13,{"path":1384,"title":1385,"description":1386,"date":1387,"series":1283,"seriesOrder":1388},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-12","软件分析与设计笔记 12：前端设计：把流程变成屏幕","界面设计不是「画得好看」：从活动图走到屏幕的三步，CRUD 任务流怎么当检查表，界面必须回答的三个问题，信息架构与五种导航类型，视觉一致性与留白，以及移动端与 Web 端的差别。","2026-11-03",12,{"path":1390,"title":1391,"description":1392,"date":1393,"series":1283,"seriesOrder":1394},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-11","软件分析与设计笔记 11：Agentic AI 与业务流程自动化","AI agent 到底是什么：五项能力与四个核心组件，什么场景该用定时任务、什么该用程序、什么才值得用 agent，两种实现路径的差别，必须提前防住的四类失败模式，以及设计方法从提示词到编排的演进。","2026-11-02",11,{"path":1396,"title":1397,"description":1398,"date":1399,"series":1283,"seriesOrder":1400},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-10","软件分析与设计笔记 10：精炼用例模型：include、extend 与泛化","三种精炼用例关系的判断标准：include 什么时候用、它和 extend 的区别到底在哪、参与者泛化与用例泛化各解决什么问题，以及精炼到什么程度就该停手。","2026-11-01",10,{"path":1402,"title":1403,"description":1404,"date":1405,"series":1283,"seriesOrder":1406},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-09","软件分析与设计笔记 09：功能需求（二）：从流程到业务实体","活动图画完之后，怎么从流程里筛出真正的业务实体：三条判断标准、哪些名词其实不该建实体、属性从哪里来，以及领域模型和后面的数据模型分别负责什么。","2026-10-31",9,{"path":1408,"title":1409,"description":1410,"date":1411,"series":1283,"seriesOrder":1412},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-08","软件分析与设计笔记 08：功能需求（一）：用例模型与活动图","从用户故事到用例的翻译过程：什么才算「好」的参与者，系统边界怎么划，用例描述必须写清哪四件事，活动图怎么把分支、并行与泳道画清楚，以及这两个图在 draw.io 里各用哪个形状、最容易画错哪一笔。","2026-10-30",8,{"path":1414,"title":1415,"description":1416,"date":1417,"series":1283,"seriesOrder":1418},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-07","软件分析与设计笔记 07：用户故事：把需求写成可验收的一句话","用户故事的三段结构，怎么从用户画像和数据地图里把它提取出来，一条故事多大算合适、时序为什么也是需求的一部分，以及验收标准怎么写才算真的可验收。","2026-10-29",7,{"path":1420,"title":1421,"description":1422,"date":1423,"series":1283,"seriesOrder":1424},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-06","软件分析与设计笔记 06：范围界定（数据侧）：用数据校验你的判断","访谈结论和数字对不上时该怎么办：分析的四个层次分别在回答什么问题、为什么不能跳级、数据准备与搜集怎么做成一张应用地图，以及数据最容易在哪几个地方骗你。","2026-10-28",6,{"path":1426,"title":1427,"description":1428,"date":1429,"series":1283,"seriesOrder":1430},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-05","软件分析与设计笔记 05：范围界定（用户侧）：用户画像与客户旅程图","为什么「所有用户」是一个陷阱，项目边界该怎么划，用户画像要写哪五个要素、最容易写成什么废纸，客户旅程图怎么把阶段、想法和情绪画在同一张纸上，以及怎么从这两样东西一步步推到可验证的需求。","2026-10-27",5,{"path":1432,"title":1433,"description":1434,"date":1435,"series":1283,"seriesOrder":1436},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-04","软件分析与设计笔记 04：接手陌生案例的第一步：三问与一张表","拿到一个完全陌生的业务案例，怎么从零开始不跑偏：只问 Who、What、Why 三个问题，把「问题—事实—假设—理由」记成一张四列表，说清为什么假设必须写明理由，以及带着问题进访谈为什么比空手去有效得多。","2026-10-26",4,{"path":1438,"title":1439,"description":1440,"date":1441,"series":1283,"seriesOrder":1248},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-03","软件分析与设计笔记 03：Agile 与 Scrum：迭代、角色与产出物","从「三周做完交付，客户说这不是我要的」讲起，说清瀑布式与迭代式的根本差别，Scrum 的三个角色和三种产出物，一个 sprint 里四次固定活动各自在解决什么问题，以及敏捷里最常见的几个误解。","2026-10-25",{"path":1443,"title":1444,"description":1445,"date":1446,"series":1283,"seriesOrder":1251},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-02","软件分析与设计笔记 02：设计思维、用户驱动与数据驱动","从「客服要一个能一次看完所有排期的界面」这个翻车案例讲起，说清设计思维到底是什么、用户驱动与数据驱动各自能看见什么又看不见什么、为什么真正的落点是把两者合起来的 data-informed design，以及访谈中怎么问出有用的信息。","2026-10-24",{"path":1448,"title":1449,"description":1450,"date":1451,"series":1283,"seriesOrder":1452},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-01","软件分析与设计笔记 01：课程地图与从需求到设计的主干","这一篇不写具体方法，先把整条链路铺开：软件分析与设计到底要解决什么问题，从需求到设计分成哪两段，中间有哪十几个环节，一份完整的交付物里应该有什么，以及后面 27 篇笔记怎么排、新人该按什么顺序读。","2026-10-23",1,{"path":1454,"title":1455,"description":1456,"date":1457,"series":1458,"seriesOrder":1452},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-01","Spring 学习笔记 01：IoC、依赖注入与第一个 Spring Boot 应用","从「手写 new 出来的三层结构」的痛点讲起，说清 IoC 容器、Bean 与依赖注入是怎么回事，再用 Maven 与 Spring Initializr 跑起第一个能返回 JSON 的应用。","2026-10-15","Spring 学习笔记",{"path":1460,"title":1461,"description":1462,"date":1457,"series":1458,"seriesOrder":1251},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-02","Spring 学习笔记 02：Spring Data JPA 基础——实体映射与 Repository","用 ORM 把类映射到表：@Entity、@Id、@GeneratedValue 怎么用，CrudRepository 三层接口怎么选，H2 与 MySQL 怎么配，以及 ddl-auto 到底该写哪个值。",{"path":1464,"title":1465,"description":1466,"date":1457,"series":1458,"seriesOrder":1248},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-03","Spring 学习笔记 03：JPA 查询——派生方法、JPQL 与分页排序","Repository 自带的方法只够按主键查，这篇讲三种自己造查询的方式：方法名派生、@Query 写 JPQL、原生 SQL，以及 Pageable 分页排序和投影返回。",{"path":1468,"title":1469,"description":1470,"date":1457,"series":1458,"seriesOrder":1436},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-04","Spring 学习笔记 04：JPA 关联映射与仓储测试","一对多、多对多怎么映射，谁负责维护外键，FetchType 与 CascadeType 该怎么选，顺带用 @DataJpaTest 与 AssertJ 把仓储层测起来。",{"path":1472,"title":1473,"description":1474,"date":1457,"series":1458,"seriesOrder":1430},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-05","Spring 学习笔记 05：Spring MVC 与 Thymeleaf 入门","一次页面请求在 Spring MVC 里走了哪几步，@Controller 怎么把数据交给模板，@PathVariable 与 @RequestParam 怎么取参数，以及控制器为什么必须是无状态的。",{"path":1476,"title":1477,"description":1478,"date":1457,"series":1458,"seriesOrder":1424},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-06","Spring 学习笔记 06：表单绑定与服务端校验","把表单字段绑到对象上，用 Bean Validation 注解做服务端校验，把错误信息显示回用户刚填的那一页，再补上自定义校验注解与全局异常处理。",{"path":1480,"title":1481,"description":1482,"date":1457,"series":1458,"seriesOrder":1418},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-07","Spring 学习笔记 07：Thymeleaf 片段复用与国际化","用 th:fragment 与 th:insert\u002Fth:replace 把导航栏、页脚抽成片段，页面套用统一布局，再用 messages.properties 与 LocaleResolver 做中英文切换。",{"path":1484,"title":1485,"description":1486,"date":1457,"series":1458,"seriesOrder":1412},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-08","Spring 学习笔记 08：HTTP Session 与会话状态管理","HTTP 无状态带来的问题，Cookie 与 Session 怎么分工，Spring 里读写会话的几种方式，以及会话固定、超时、多实例共享这些必须想清楚的事。",{"path":1488,"title":1489,"description":1490,"date":1457,"series":1458,"seriesOrder":1406},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-09","Spring 学习笔记 09：拦截器 HandlerInterceptor","登录检查、权限判断、请求耗时统计这类横切逻辑该放哪：HandlerInterceptor 的三个回调各在什么时机执行，怎么注册与排除路径，以及它和过滤器的分工。",{"path":1492,"title":1493,"description":1494,"date":1457,"series":1458,"seriesOrder":1400},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-10","Spring 学习笔记 10：Service 层与事务","一组数据库操作要么全成功要么全失败，靠 @Transactional 怎么做到；传播行为、隔离级别、回滚规则怎么选，以及自调用失效、乐观锁悲观锁这些实战里的坑。",{"path":1496,"title":1497,"description":1498,"date":1457,"series":1458,"seriesOrder":1394},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-11","Spring 学习笔记 11：REST API 基础——把 Service 暴露成接口","同一套 Service 怎么同时服务页面和客户端：@RestController 的路由与方法语义、状态码怎么给、DTO 怎么定，以及 MockMvc 端到端和切片测试的两种写法与各自的坑。",{"path":1500,"title":1501,"description":1502,"date":1457,"series":1458,"seriesOrder":1388},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-12","Spring 学习笔记 12：REST 进阶——版本、更新、跨域与文档化","接口能跑通之后要处理的几件事：URL 与响应结构怎么定、版本怎么演进、更新怎么做到可安全重试、CORS 怎么配、HATEOAS 与 Spring Data REST 该不该用。",{"path":1504,"title":1505,"description":1506,"date":1507,"series":1508,"seriesOrder":1436},"\u002Fposts\u002Fjava\u002Fjava-functionalprogramming-notes","Java 函数式编程笔记：Lambda、函数式接口与 Streams","学习 Java 函数式编程时的个人笔记：Lambda 表达式的五步简写与方法引用、常见函数式接口、Stream 流水线与惰性求值，以及创建流、中间操作、终止操作与收集器的用法。示例代码均为自写，运行结果来自本地实测。","2026-10-13","Java 学习笔记",{"path":1510,"title":1511,"description":1512,"date":1507,"series":1508,"seriesOrder":1248},"\u002Fposts\u002Fjava\u002Fjava-generics-notes","Java 泛型笔记：泛型方法、有界类型与泛型类","学习 Java 泛型时的个人笔记：从重复的重载方法与多态尝试讲起，再到泛型方法与类型推断、有界泛型类型、Comparable 与 Number 边界，以及泛型类在集合中的用法。示例代码均为自写，运行结果来自本地实测。",{"path":1514,"title":1515,"description":1516,"date":1517,"series":1518,"seriesOrder":1436},"\u002Fposts\u002Fmysql\u002Fmysql-learning-04","MySQL 学习笔记 04：数据操纵（增删改）","MySQL 学习笔记第四篇：INSERT 插入（含批量插入与 INSERT ... SELECT）、UPDATE 更新、DELETE 删除，以及各自的坑。","2026-10-10","SQL 学习笔记",{"path":1520,"title":1521,"description":1522,"date":1517,"series":1518,"seriesOrder":1430},"\u002Fposts\u002Fmysql\u002Fmysql-learning-05","MySQL 学习笔记 05：DDL 概览","MySQL 学习笔记第五篇：数据定义语言概览——Schema、Table、View、Index、Constraints 这几类对象分别对应哪些 CREATE \u002F ALTER \u002F DROP 命令。",{"path":1524,"title":1525,"description":1526,"date":1517,"series":1518,"seriesOrder":1424},"\u002Fposts\u002Fmysql\u002Fmysql-learning-06","MySQL 学习笔记 06：CREATE TABLE 与数据类型","MySQL 学习笔记第六篇：CREATE TABLE 的完整语法、常用数据类型的选型建议，以及外键的两种定义方式。",{"path":1528,"title":1529,"description":1530,"date":1517,"series":1518,"seriesOrder":1418},"\u002Fposts\u002Fmysql\u002Fmysql-learning-07","MySQL 学习笔记 07：索引","MySQL 学习笔记第七篇：索引的作用与 B+ 树结构、单列与复合索引的创建、索引的代价与适用场景。",{"path":1532,"title":1533,"description":1534,"date":1517,"series":1518,"seriesOrder":1412},"\u002Fposts\u002Fmysql\u002Fmysql-learning-08","MySQL 学习笔记 08：DROP 与 ALTER","MySQL 学习笔记第八篇：DROP 删除数据库对象、ALTER 修改表结构，以及常见的坑。",{"path":1536,"title":1537,"description":1538,"date":1517,"series":1518,"seriesOrder":1406},"\u002Fposts\u002Fmysql\u002Fmysql-learning-09","MySQL 学习笔记 09：约束与数据完整性","MySQL 学习笔记第九篇：SQL 约束——NOT NULL、CHECK、PRIMARY KEY、FOREIGN KEY 等如何保证数据完整性，以及它们的对比。",{"path":1540,"title":1541,"description":1542,"date":1517,"series":1518,"seriesOrder":1400},"\u002Fposts\u002Fmysql\u002Fmysql-learning-10","MySQL 学习笔记 10：视图","MySQL 学习笔记第十篇：视图（Views）的定义与特性、和表的对比、创建与使用，以及可更新视图的限制。",{"path":1544,"title":1545,"description":1546,"date":1517,"series":1518,"seriesOrder":1394},"\u002Fposts\u002Fmysql\u002Fmysql-learning-11","MySQL 学习笔记 11：存储过程","MySQL 学习笔记第十一篇：存储过程（Stored Procedures）的概念、创建与调用、带参数的写法，以及优缺点。",{"path":1548,"title":1549,"description":1550,"date":1551,"series":1518,"seriesOrder":1452},"\u002Fposts\u002Fmysql\u002Fmysql-learning-01","MySQL 学习笔记 01：MySQL 基础知识","MySQL 学习笔记第一篇：MySQL Server 与 Workbench 的安装，数据库与 DBMS 的基本概念，关系模型、ACID 事务、常见数据类型，以及后续的学习路线。","2026-10-09",{"path":1553,"title":1554,"description":1555,"date":1551,"series":1518,"seriesOrder":1251},"\u002Fposts\u002Fmysql\u002Fmysql-learning-02","MySQL 学习笔记 02：SELECT 查询与过滤","MySQL 学习笔记第二篇：SELECT 的基本语法与逻辑执行顺序，WHERE 过滤（比较、LIKE、IN、BETWEEN、NULL）、各种 JOIN、UNION 与子查询。",{"path":1557,"title":1558,"description":1559,"date":1551,"series":1518,"seriesOrder":1248},"\u002Fposts\u002Fmysql\u002Fmysql-learning-03","MySQL 学习笔记 03：聚合、分组与输出控制","MySQL 学习笔记第三篇：COUNT\u002FSUM\u002FAVG 等聚合函数、GROUP BY 分组、HAVING 过滤、ORDER BY 与 LIMIT，以及一条 SELECT 的完整逻辑执行顺序。",{"path":1561,"title":1562,"description":1563,"date":1564,"series":1508,"seriesOrder":1251},"\u002Fposts\u002Fjava\u002Fjava-oop-notes","Java 面向对象笔记：类、继承、多态与异常","学习 Java 面向对象编程时的个人笔记：类与对象、封装与访问修饰符、继承与多态、集合、抽象类与接口、异常处理。示例代码均为自写，运行结果来自本地实测。","2026-10-06",{"path":1566,"title":1567,"description":1568,"date":1569,"series":1508,"seriesOrder":1452},"\u002Fposts\u002Fjava\u002Fjava-basics-notes","Java 基础语法笔记：从环境安装到方法与数组","学习 Java 基础语法时的个人笔记：JDK 安装、输入输出、变量与运算符、条件与循环、字符串、数组与方法。示例代码均为自写，运行结果来自本地实测。","2026-10-05",{"path":1571,"title":1572,"description":1573,"date":1574,"series":1575,"seriesOrder":1452},"\u002Fposts\u002Fgit\u002Fgit-install-and-basics","Git 安装与基本使用","从装好 Git 到跑通日常提交流程，把最常用的几条命令和它们背后的三个区域讲清楚。","2026-10-04","Git 入门",{"path":1577,"title":1578,"description":1579,"date":1574,"series":1575,"seriesOrder":1251},"\u002Fposts\u002Fgit\u002Flocal-remote-repository","本地远程仓库的部署","用自己的机器当 Git 服务器：建一个裸仓库、走 SSH 连上去，把本地提交推上去。",{"path":1581,"title":1582,"description":1583,"date":1574,"series":1584,"seriesOrder":1452},"\u002Fposts\u002Fguide\u002Fguide-file-layout","文件都放在哪","文章、图片、封面分别放在仓库的什么位置，哪些目录可以放手改、哪些碰了会出问题。","博客使用指南",{"path":1586,"title":1587,"description":1588,"date":1574,"series":1584,"seriesOrder":1251},"\u002Fposts\u002Fguide\u002Fguide-frontmatter","frontmatter 每个字段是什么意思","文章开头那段被横线包住的字段，逐个说明作用、默认行为和写错的后果。",{"path":1590,"title":1591,"description":1592,"date":1574,"series":1584,"seriesOrder":1248},"\u002Fposts\u002Fguide\u002Fguide-markdown","正文怎么写 —— Markdown 与排版效果","标题、链接、图片、代码、表格分别怎么写，以及本站对它们做了哪些自定义处理。",1793268185375]