[{"data":1,"prerenderedAt":2382},["ShallowReactive",2],{"post:\u002Fposts\u002Fmysql\u002Fmysql-learning-11":3,"posts:ordered":2092},{"id":4,"title":5,"body":6,"category":2077,"cover":2078,"date":1472,"description":2079,"extension":2080,"featured":2081,"meta":2082,"navigation":673,"path":2083,"seo":2084,"series":2085,"seriesOrder":1030,"stem":2086,"tags":2087,"__hash__":2091},"posts\u002Fposts\u002Fmysql\u002Fmysql-learning-11.md","MySQL 学习笔记 11：存储过程",{"type":7,"value":8,"toc":2052},"minimark",[9,16,21,26,31,100,104,161,165,195,199,227,231,258,262,299,302,306,313,316,330,333,344,347,358,360,364,441,443,511,514,533,540,542,557,560,573,575,579,682,684,707,710,734,736,750,788,791,817,820,841,843,869,871,882,884,888,892,1080,1082,1134,1136,1139,1163,1166,1193,1196,1220,1222,1236,1238,1242,1351,1353,1393,1395,1397,1400,1418,1420,1444,1446,1449,1473,1475,1494,1496,1507,1509,1513,1631,1633,1650,1652,1671,1673,1695,1715,1718,1720,1731,1733,1737,1740,1746,1749,1753,1822,1833,1837,1877,1888,1892,1903,1907,1975,1986,1988,1992,2039,2042,2048],[10,11,12],"blockquote",{},[13,14,15],"p",{},"本文是《SQL 学习笔记》系列的一篇：示例数据和 SQL 都是我整理并在 MySQL 8 上跑过的，讲解部分按自己的理解写的。",[17,18,20],"h1",{"id":19},"_14-存储过程-stored-procedures","14 存储过程 (Stored Procedures)",[22,23,25],"h2",{"id":24},"_140-示例数据","14.0 示例数据",[27,28,30],"h3",{"id":29},"customers-表","Customers 表",[32,33,34,54],"table",{},[35,36,37],"thead",{},[38,39,40,45,48,51],"tr",{},[41,42,44],"th",{"align":43},"right","Id",[41,46,47],{},"Name",[41,49,50],{},"Race",[41,52,53],{"align":43},"Age",[55,56,57,72,86],"tbody",{},[38,58,59,63,66,69],{},[60,61,62],"td",{"align":43},"1",[60,64,65],{},"Chia",[60,67,68],{},"CN",[60,70,71],{"align":43},"22",[38,73,74,77,80,83],{},[60,75,76],{"align":43},"2",[60,78,79],{},"Tom",[60,81,82],{},"ML",[60,84,85],{"align":43},"18",[38,87,88,91,94,97],{},[60,89,90],{"align":43},"3",[60,92,93],{},"Alice",[60,95,96],{},"IND",[60,98,99],{"align":43},"25",[27,101,103],{"id":102},"books-表","Books 表",[32,105,106,122],{},[35,107,108],{},[38,109,110,113,116,119],{},[41,111,112],{"align":43},"BookId",[41,114,115],{},"Title",[41,117,118],{},"Rating",[41,120,121],{"align":43},"TotalStock",[55,123,124,137,150],{},[38,125,126,128,131,134],{},[60,127,62],{"align":43},[60,129,130],{},"Effective Java",[60,132,133],{},"G",[60,135,136],{"align":43},"10",[38,138,139,141,144,147],{},[60,140,76],{"align":43},[60,142,143],{},"Clean Code",[60,145,146],{},"PG",[60,148,149],{"align":43},"5",[38,151,152,154,157,159],{},[60,153,90],{"align":43},[60,155,156],{},"Refactoring",[60,158,133],{},[60,160,90],{"align":43},[27,162,164],{"id":163},"accounts-表","Accounts 表",[32,166,167,177],{},[35,168,169],{},[38,170,171,174],{},[41,172,173],{"align":43},"AccountID",[41,175,176],{"align":43},"Balance",[55,178,179,187],{},[38,180,181,184],{},[60,182,183],{"align":43},"101",[60,185,186],{"align":43},"1000.00",[38,188,189,192],{},[60,190,191],{"align":43},"202",[60,193,194],{"align":43},"500.00",[27,196,198],{"id":197},"products-表","Products 表",[32,200,201,211],{},[35,202,203],{},[38,204,205,208],{},[41,206,207],{"align":43},"ProductID",[41,209,210],{"align":43},"Stock",[55,212,213,220],{},[38,214,215,217],{},[60,216,62],{"align":43},[60,218,219],{"align":43},"20",[38,221,222,224],{},[60,223,76],{"align":43},[60,225,226],{"align":43},"15",[27,228,230],{"id":229},"orders-表","Orders 表",[32,232,233,246],{},[35,234,235],{},[38,236,237,240,243],{},[41,238,239],{"align":43},"OrderId",[41,241,242],{"align":43},"CustomerID",[41,244,245],{},"OrderDate",[55,247,248],{},[38,249,250,253,255],{},[60,251,252],{"align":43},"1001",[60,254,62],{"align":43},[60,256,257],{},"2026-10-01",[27,259,261],{"id":260},"users-表","Users 表",[32,263,264,277],{},[35,265,266],{},[38,267,268,271,274],{},[41,269,270],{"align":43},"UserId",[41,272,273],{},"Username",[41,275,276],{},"Password",[55,278,279,289],{},[38,280,281,283,286],{},[60,282,62],{"align":43},[60,284,285],{},"john",[60,287,288],{},"1234",[38,290,291,293,296],{},[60,292,76],{"align":43},[60,294,295],{},"alice",[60,297,298],{},"abcd",[300,301],"hr",{},[22,303,305],{"id":304},"_141-存储过程概念","14.1 存储过程概念",[307,308,309],"ul",{},[310,311,312],"li",{},"一组 SQL 语句和过程逻辑，编译后存储在数据库中。",[13,314,315],{},"注释：",[307,317,318,321,324,327],{},[310,319,320],{},"存储过程（Stored Procedure）是数据库中的一段可执行程序。",[310,322,323],{},"它可以包含多条 SQL 语句、变量、条件判断、循环、事务控制等。",[310,325,326],{},"存储过程在数据库服务器端执行，客户端只需要调用它。",[310,328,329],{},"存储过程有名称，可以带参数。",[13,331,332],{},"示例：",[334,335,341],"pre",{"className":336,"code":338,"language":339,"meta":340},[337],"language-text","存储过程 = 数据库里的“函数”或“脚本”。\n一次定义，多次调用。\n","text","",[342,343,338],"code",{"__ignoreMap":340},[13,345,346],{},"作用：",[307,348,349,352,355],{},[310,350,351],{},"封装复杂逻辑。",[310,353,354],{},"减少应用层与数据库之间的往返。",[310,356,357],{},"提高安全性和可重用性。",[300,359],{},[22,361,363],{"id":362},"_142-创建存储过程","14.2 创建存储过程",[334,365,369],{"className":366,"code":367,"language":368,"meta":340,"style":340},"language-sql shiki shiki-themes github-light github-dark","DELIMITER $$\nCREATE PROCEDURE sp_list_customers()\nBEGIN\n  SELECT * FROM Customers;\n  statement2;\n  statement3;\nEND $$\nDELIMITER ;\n","sql",[342,370,371,380,393,399,414,420,426,435],{"__ignoreMap":340},[372,373,376],"span",{"class":374,"line":375},"line",1,[372,377,379],{"class":378},"sVt8B","DELIMITER $$\n",[372,381,383,387,390],{"class":374,"line":382},2,[372,384,386],{"class":385},"szBVR","CREATE",[372,388,389],{"class":385}," PROCEDURE",[372,391,392],{"class":378}," sp_list_customers()\n",[372,394,396],{"class":374,"line":395},3,[372,397,398],{"class":385},"BEGIN\n",[372,400,402,405,408,411],{"class":374,"line":401},4,[372,403,404],{"class":385},"  SELECT",[372,406,407],{"class":385}," *",[372,409,410],{"class":385}," FROM",[372,412,413],{"class":378}," Customers;\n",[372,415,417],{"class":374,"line":416},5,[372,418,419],{"class":378},"  statement2;\n",[372,421,423],{"class":374,"line":422},6,[372,424,425],{"class":378},"  statement3;\n",[372,427,429,432],{"class":374,"line":428},7,[372,430,431],{"class":385},"END",[372,433,434],{"class":378}," $$\n",[372,436,438],{"class":374,"line":437},8,[372,439,440],{"class":378},"DELIMITER ;\n",[13,442,315],{},[307,444,445,459,468,478,484,494,503],{},[310,446,447,450,451,454,455,458],{},[342,448,449],{},"DELIMITER $$","：把语句结束符从 ",[342,452,453],{},";"," 临时改成 ",[342,456,457],{},"$$","。",[310,460,461,462,464,465,467],{},"原因：存储过程体内有多条 SQL，每条以 ",[342,463,453],{}," 结尾。如果不改结束符，数据库会在第一条 ",[342,466,453],{}," 处误认为存储过程结束。",[310,469,470,473,474,477],{},[342,471,472],{},"CREATE PROCEDURE sp_list_customers()","：创建名为 ",[342,475,476],{},"sp_list_customers"," 的存储过程，无参数。",[310,479,480,483],{},[342,481,482],{},"BEGIN ... END","：过程体，包含多条语句。",[310,485,486,489,490,493],{},[342,487,488],{},"statement2;","、",[342,491,492],{},"statement3;","：占位，实际使用时替换为具体 SQL。",[310,495,496,499,500,502],{},[342,497,498],{},"END $$","：用 ",[342,501,457],{}," 结束存储过程定义。",[310,504,505,508,509,458],{},[342,506,507],{},"DELIMITER ;","：把结束符恢复为 ",[342,510,453],{},[13,512,513],{},"执行效果：",[334,515,517],{"className":366,"code":516,"language":368,"meta":340,"style":340},"-- 调用无参存储过程\nCALL sp_list_customers();\n",[342,518,519,525],{"__ignoreMap":340},[372,520,521],{"class":374,"line":375},[372,522,524],{"class":523},"sJ8bj","-- 调用无参存储过程\n",[372,526,527,530],{"class":374,"line":382},[372,528,529],{"class":385},"CALL",[372,531,532],{"class":378}," sp_list_customers();\n",[13,534,535,536,539],{},"结果：执行过程体内的 ",[342,537,538],{},"SELECT * FROM Customers;","，返回 Customers 全部行。",[13,541,346],{},[307,543,544,547,554],{},[310,545,546],{},"把多条 SQL 打包成一个过程。",[310,548,549,550,553],{},"通过 ",[342,551,552],{},"CALL 过程名()"," 调用。",[310,555,556],{},"避免每次重复写相同 SQL。",[13,558,559],{},"注意：",[307,561,562,568],{},[310,563,564,567],{},[342,565,566],{},"DELIMITER"," 是 MySQL 客户端命令，不是标准 SQL。",[310,569,570,571,458],{},"其他数据库（如 SQL Server、PostgreSQL）语法不同，不使用 ",[342,572,566],{},[300,574],{},[22,576,578],{"id":577},"_143-带参数的存储过程","14.3 带参数的存储过程",[334,580,582],{"className":366,"code":581,"language":368,"meta":340,"style":340},"DELIMITER \u002F\u002F\nCREATE PROCEDURE MyProcedure (var1 CHAR(2), var2 INTEGER)\nBEGIN\n  SELECT * FROM Books WHERE Rating = var1 AND TotalStock > var2;\nEND \u002F\u002F\nDELIMITER ;\n\nCALL MyProcedure(val1, val2);\n",[342,583,584,592,619,623,658,665,669,675],{"__ignoreMap":340},[372,585,586,589],{"class":374,"line":375},[372,587,588],{"class":378},"DELIMITER ",[372,590,591],{"class":385},"\u002F\u002F\n",[372,593,594,596,598,601,604,607,610,613,616],{"class":374,"line":382},[372,595,386],{"class":385},[372,597,389],{"class":385},[372,599,600],{"class":378}," MyProcedure (var1 ",[372,602,603],{"class":385},"CHAR",[372,605,606],{"class":378},"(",[372,608,76],{"class":609},"sj4cs",[372,611,612],{"class":378},"), var2 ",[372,614,615],{"class":385},"INTEGER",[372,617,618],{"class":378},")\n",[372,620,621],{"class":374,"line":395},[372,622,398],{"class":385},[372,624,625,627,629,631,634,637,640,643,646,649,652,655],{"class":374,"line":401},[372,626,404],{"class":385},[372,628,407],{"class":385},[372,630,410],{"class":385},[372,632,633],{"class":378}," Books ",[372,635,636],{"class":385},"WHERE",[372,638,639],{"class":378}," Rating ",[372,641,642],{"class":385},"=",[372,644,645],{"class":378}," var1 ",[372,647,648],{"class":385},"AND",[372,650,651],{"class":378}," TotalStock ",[372,653,654],{"class":385},">",[372,656,657],{"class":378}," var2;\n",[372,659,660,662],{"class":374,"line":416},[372,661,431],{"class":385},[372,663,664],{"class":385}," \u002F\u002F\n",[372,666,667],{"class":374,"line":422},[372,668,440],{"class":378},[372,670,671],{"class":374,"line":428},[372,672,674],{"emptyLinePlaceholder":673},true,"\n",[372,676,677,679],{"class":374,"line":437},[372,678,529],{"class":385},[372,680,681],{"class":378}," MyProcedure(val1, val2);\n",[13,683,315],{},[307,685,686,692,698,701],{},[310,687,688,691],{},[342,689,690],{},"var1 CHAR(2)","：第一个参数，字符类型，长度 2。",[310,693,694,697],{},[342,695,696],{},"var2 INTEGER","：第二个参数，整数类型。",[310,699,700],{},"参数在过程体内可以像普通列一样使用。",[310,702,703,706],{},[342,704,705],{},"CALL MyProcedure(val1, val2);","：用实际值调用存储过程。",[13,708,709],{},"示例调用：",[334,711,713],{"className":366,"code":712,"language":368,"meta":340,"style":340},"CALL MyProcedure('G', 5);\n",[342,714,715],{"__ignoreMap":340},[372,716,717,719,722,726,729,731],{"class":374,"line":375},[372,718,529],{"class":385},[372,720,721],{"class":378}," MyProcedure(",[372,723,725],{"class":724},"sZZnC","'G'",[372,727,728],{"class":378},", ",[372,730,149],{"class":609},[372,732,733],{"class":378},");\n",[13,735,513],{},[307,737,738,747],{},[310,739,740,743,744,458],{},[342,741,742],{},"var1 = 'G'","，",[342,745,746],{},"var2 = 5",[310,748,749],{},"等价于执行：",[334,751,753],{"className":366,"code":752,"language":368,"meta":340,"style":340},"SELECT * FROM Books WHERE Rating = 'G' AND TotalStock > 5;\n",[342,754,755],{"__ignoreMap":340},[372,756,757,760,762,764,766,768,770,772,775,778,780,782,785],{"class":374,"line":375},[372,758,759],{"class":385},"SELECT",[372,761,407],{"class":385},[372,763,410],{"class":385},[372,765,633],{"class":378},[372,767,636],{"class":385},[372,769,639],{"class":378},[372,771,642],{"class":385},[372,773,774],{"class":724}," 'G'",[372,776,777],{"class":385}," AND",[372,779,651],{"class":378},[372,781,654],{"class":385},[372,783,784],{"class":609}," 5",[372,786,787],{"class":378},";\n",[13,789,790],{},"结果：",[32,792,793,805],{},[35,794,795],{},[38,796,797,799,801,803],{},[41,798,112],{"align":43},[41,800,115],{},[41,802,118],{},[41,804,121],{"align":43},[55,806,807],{},[38,808,809,811,813,815],{},[60,810,62],{"align":43},[60,812,130],{},[60,814,133],{},[60,816,136],{"align":43},[13,818,819],{},"再调用：",[334,821,823],{"className":366,"code":822,"language":368,"meta":340,"style":340},"CALL MyProcedure('PG', 0);\n",[342,824,825],{"__ignoreMap":340},[372,826,827,829,831,834,836,839],{"class":374,"line":375},[372,828,529],{"class":385},[372,830,721],{"class":378},[372,832,833],{"class":724},"'PG'",[372,835,728],{"class":378},[372,837,838],{"class":609},"0",[372,840,733],{"class":378},[13,842,790],{},[32,844,845,857],{},[35,846,847],{},[38,848,849,851,853,855],{},[41,850,112],{"align":43},[41,852,115],{},[41,854,118],{},[41,856,121],{"align":43},[55,858,859],{},[38,860,861,863,865,867],{},[60,862,76],{"align":43},[60,864,143],{},[60,866,146],{},[60,868,149],{"align":43},[13,870,346],{},[307,872,873,876,879],{},[310,874,875],{},"同一个存储过程可以根据不同参数返回不同结果。",[310,877,878],{},"提高复用性。",[310,880,881],{},"避免把过滤条件硬编码在过程体内。",[300,883],{},[22,885,887],{"id":886},"_144-存储过程示例","14.4 存储过程示例",[27,889,891],{"id":890},"_1441-银行转账","14.4.1 银行转账",[334,893,895],{"className":366,"code":894,"language":368,"meta":340,"style":340},"DELIMITER $$\nCREATE PROCEDURE TransferMoney(\n  fromAccount INT,\n  toAccount INT,\n  amount DECIMAL(10,2)\n)\nBEGIN\n  START TRANSACTION;\n  UPDATE Accounts SET Balance = Balance - amount WHERE AccountID = fromAccount;\n  UPDATE Accounts SET Balance = Balance + amount WHERE AccountID = toAccount;\n  COMMIT;\nEND $$\nDELIMITER ;\n\nCALL TransferMoney(101, 202, 500.00);\n",[342,896,897,901,910,921,930,949,953,957,964,999,1028,1036,1043,1048,1053],{"__ignoreMap":340},[372,898,899],{"class":374,"line":375},[372,900,379],{"class":378},[372,902,903,905,907],{"class":374,"line":382},[372,904,386],{"class":385},[372,906,389],{"class":385},[372,908,909],{"class":378}," TransferMoney(\n",[372,911,912,915,918],{"class":374,"line":395},[372,913,914],{"class":378},"  fromAccount ",[372,916,917],{"class":385},"INT",[372,919,920],{"class":378},",\n",[372,922,923,926,928],{"class":374,"line":401},[372,924,925],{"class":378},"  toAccount ",[372,927,917],{"class":385},[372,929,920],{"class":378},[372,931,932,935,938,940,942,945,947],{"class":374,"line":416},[372,933,934],{"class":378},"  amount ",[372,936,937],{"class":385},"DECIMAL",[372,939,606],{"class":378},[372,941,136],{"class":609},[372,943,944],{"class":378},",",[372,946,76],{"class":609},[372,948,618],{"class":378},[372,950,951],{"class":374,"line":422},[372,952,618],{"class":378},[372,954,955],{"class":374,"line":428},[372,956,398],{"class":385},[372,958,959,962],{"class":374,"line":437},[372,960,961],{"class":385},"  START TRANSACTION",[372,963,787],{"class":378},[372,965,967,970,973,976,979,981,983,986,989,991,994,996],{"class":374,"line":966},9,[372,968,969],{"class":385},"  UPDATE",[372,971,972],{"class":378}," Accounts ",[372,974,975],{"class":385},"SET",[372,977,978],{"class":378}," Balance ",[372,980,642],{"class":385},[372,982,978],{"class":378},[372,984,985],{"class":385},"-",[372,987,988],{"class":378}," amount ",[372,990,636],{"class":385},[372,992,993],{"class":378}," AccountID ",[372,995,642],{"class":385},[372,997,998],{"class":378}," fromAccount;\n",[372,1000,1002,1004,1006,1008,1010,1012,1014,1017,1019,1021,1023,1025],{"class":374,"line":1001},10,[372,1003,969],{"class":385},[372,1005,972],{"class":378},[372,1007,975],{"class":385},[372,1009,978],{"class":378},[372,1011,642],{"class":385},[372,1013,978],{"class":378},[372,1015,1016],{"class":385},"+",[372,1018,988],{"class":378},[372,1020,636],{"class":385},[372,1022,993],{"class":378},[372,1024,642],{"class":385},[372,1026,1027],{"class":378}," toAccount;\n",[372,1029,1031,1034],{"class":374,"line":1030},11,[372,1032,1033],{"class":385},"  COMMIT",[372,1035,787],{"class":378},[372,1037,1039,1041],{"class":374,"line":1038},12,[372,1040,431],{"class":385},[372,1042,434],{"class":378},[372,1044,1046],{"class":374,"line":1045},13,[372,1047,440],{"class":378},[372,1049,1051],{"class":374,"line":1050},14,[372,1052,674],{"emptyLinePlaceholder":673},[372,1054,1056,1058,1061,1063,1065,1067,1069,1072,1075,1078],{"class":374,"line":1055},15,[372,1057,529],{"class":385},[372,1059,1060],{"class":378}," TransferMoney(",[372,1062,183],{"class":609},[372,1064,728],{"class":378},[372,1066,191],{"class":609},[372,1068,728],{"class":378},[372,1070,1071],{"class":609},"500",[372,1073,1074],{"class":378},".",[372,1076,1077],{"class":609},"00",[372,1079,733],{"class":378},[13,1081,315],{},[307,1083,1084,1090,1096,1106,1112,1119,1125,1131],{},[310,1085,1086,1089],{},[342,1087,1088],{},"fromAccount","：转出账户 ID。",[310,1091,1092,1095],{},[342,1093,1094],{},"toAccount","：转入账户 ID。",[310,1097,1098,1101,1102,1105],{},[342,1099,1100],{},"amount","：转账金额，",[342,1103,1104],{},"DECIMAL(10,2)"," 精确数值，适合金额。",[310,1107,1108,1111],{},[342,1109,1110],{},"START TRANSACTION","：开始事务。",[310,1113,1114,1115,1118],{},"第一条 ",[342,1116,1117],{},"UPDATE","：转出账户减钱。",[310,1120,1121,1122,1124],{},"第二条 ",[342,1123,1117],{},"：转入账户加钱。",[310,1126,1127,1130],{},[342,1128,1129],{},"COMMIT","：提交事务，两步同时生效。",[310,1132,1133],{},"如果中途出错，可以回滚，保证两步要么都成功，要么都不做。",[13,1135,513],{},[13,1137,1138],{},"调用前：",[32,1140,1141,1149],{},[35,1142,1143],{},[38,1144,1145,1147],{},[41,1146,173],{"align":43},[41,1148,176],{"align":43},[55,1150,1151,1157],{},[38,1152,1153,1155],{},[60,1154,183],{"align":43},[60,1156,186],{"align":43},[38,1158,1159,1161],{},[60,1160,191],{"align":43},[60,1162,194],{"align":43},[13,1164,1165],{},"调用：",[334,1167,1169],{"className":366,"code":1168,"language":368,"meta":340,"style":340},"CALL TransferMoney(101, 202, 500.00);\n",[342,1170,1171],{"__ignoreMap":340},[372,1172,1173,1175,1177,1179,1181,1183,1185,1187,1189,1191],{"class":374,"line":375},[372,1174,529],{"class":385},[372,1176,1060],{"class":378},[372,1178,183],{"class":609},[372,1180,728],{"class":378},[372,1182,191],{"class":609},[372,1184,728],{"class":378},[372,1186,1071],{"class":609},[372,1188,1074],{"class":378},[372,1190,1077],{"class":609},[372,1192,733],{"class":378},[13,1194,1195],{},"调用后：",[32,1197,1198,1206],{},[35,1199,1200],{},[38,1201,1202,1204],{},[41,1203,173],{"align":43},[41,1205,176],{"align":43},[55,1207,1208,1214],{},[38,1209,1210,1212],{},[60,1211,183],{"align":43},[60,1213,194],{"align":43},[38,1215,1216,1218],{},[60,1217,191],{"align":43},[60,1219,186],{"align":43},[13,1221,346],{},[307,1223,1224,1227,1233],{},[310,1225,1226],{},"保证转账的原子性：要么两边都改，要么都不改。",[310,1228,1229,1230,458],{},"封装转账逻辑，应用层只需调用 ",[342,1231,1232],{},"CALL TransferMoney(...)",[310,1234,1235],{},"避免应用层写多条 SQL 导致不一致。",[300,1237],{},[27,1239,1241],{"id":1240},"_1442-下订单","14.4.2 下订单",[334,1243,1245],{"className":366,"code":1244,"language":368,"meta":340,"style":340},"DELIMITER $$\nCREATE PROCEDURE PlaceOrder(\n  customerId INT,\n  productId INT,\n  quantity INT\n)\nBEGIN\n  INSERT INTO Orders(CustomerID, OrderDate) VALUES(customerId, NOW());\n  UPDATE Products SET Stock = Stock - quantity WHERE ProductID = productId;\nEND $$\n",[342,1246,1247,1251,1260,1269,1278,1286,1290,1294,1314,1345],{"__ignoreMap":340},[372,1248,1249],{"class":374,"line":375},[372,1250,379],{"class":378},[372,1252,1253,1255,1257],{"class":374,"line":382},[372,1254,386],{"class":385},[372,1256,389],{"class":385},[372,1258,1259],{"class":378}," PlaceOrder(\n",[372,1261,1262,1265,1267],{"class":374,"line":395},[372,1263,1264],{"class":378},"  customerId ",[372,1266,917],{"class":385},[372,1268,920],{"class":378},[372,1270,1271,1274,1276],{"class":374,"line":401},[372,1272,1273],{"class":378},"  productId ",[372,1275,917],{"class":385},[372,1277,920],{"class":378},[372,1279,1280,1283],{"class":374,"line":416},[372,1281,1282],{"class":378},"  quantity ",[372,1284,1285],{"class":385},"INT\n",[372,1287,1288],{"class":374,"line":422},[372,1289,618],{"class":378},[372,1291,1292],{"class":374,"line":428},[372,1293,398],{"class":385},[372,1295,1296,1299,1302,1305,1308,1311],{"class":374,"line":437},[372,1297,1298],{"class":385},"  INSERT INTO",[372,1300,1301],{"class":378}," Orders(CustomerID, OrderDate) ",[372,1303,1304],{"class":385},"VALUES",[372,1306,1307],{"class":378},"(customerId, ",[372,1309,1310],{"class":385},"NOW",[372,1312,1313],{"class":378},"());\n",[372,1315,1316,1318,1321,1323,1326,1328,1330,1332,1335,1337,1340,1342],{"class":374,"line":966},[372,1317,969],{"class":385},[372,1319,1320],{"class":378}," Products ",[372,1322,975],{"class":385},[372,1324,1325],{"class":378}," Stock ",[372,1327,642],{"class":385},[372,1329,1325],{"class":378},[372,1331,985],{"class":385},[372,1333,1334],{"class":378}," quantity ",[372,1336,636],{"class":385},[372,1338,1339],{"class":378}," ProductID ",[372,1341,642],{"class":385},[372,1343,1344],{"class":378}," productId;\n",[372,1346,1347,1349],{"class":374,"line":1001},[372,1348,431],{"class":385},[372,1350,434],{"class":378},[13,1352,315],{},[307,1354,1355,1361,1367,1373,1380,1386],{},[310,1356,1357,1360],{},[342,1358,1359],{},"customerId","：下单客户 ID。",[310,1362,1363,1366],{},[342,1364,1365],{},"productId","：下单产品 ID。",[310,1368,1369,1372],{},[342,1370,1371],{},"quantity","：购买数量。",[310,1374,1375,1376,1379],{},"第一步：向 ",[342,1377,1378],{},"Orders"," 表插入一条订单记录。",[310,1381,1382,1385],{},[342,1383,1384],{},"NOW()","：返回当前时间。",[310,1387,1388,1389,1392],{},"第二步：从 ",[342,1390,1391],{},"Products"," 表中扣减对应产品库存。",[13,1394,513],{},[13,1396,1138],{},[13,1398,1399],{},"Products：",[32,1401,1402,1410],{},[35,1403,1404],{},[38,1405,1406,1408],{},[41,1407,207],{"align":43},[41,1409,210],{"align":43},[55,1411,1412],{},[38,1413,1414,1416],{},[60,1415,62],{"align":43},[60,1417,219],{"align":43},[13,1419,1165],{},[334,1421,1423],{"className":366,"code":1422,"language":368,"meta":340,"style":340},"CALL PlaceOrder(1, 1, 3);\n",[342,1424,1425],{"__ignoreMap":340},[372,1426,1427,1429,1432,1434,1436,1438,1440,1442],{"class":374,"line":375},[372,1428,529],{"class":385},[372,1430,1431],{"class":378}," PlaceOrder(",[372,1433,62],{"class":609},[372,1435,728],{"class":378},[372,1437,62],{"class":609},[372,1439,728],{"class":378},[372,1441,90],{"class":609},[372,1443,733],{"class":378},[13,1445,1195],{},[13,1447,1448],{},"Orders 新增一行：",[32,1450,1451,1461],{},[35,1452,1453],{},[38,1454,1455,1457,1459],{},[41,1456,239],{"align":43},[41,1458,242],{"align":43},[41,1460,245],{},[55,1462,1463],{},[38,1464,1465,1468,1470],{},[60,1466,1467],{"align":43},"1002",[60,1469,62],{"align":43},[60,1471,1472],{},"2026-10-10",[13,1474,1399],{},[32,1476,1477,1485],{},[35,1478,1479],{},[38,1480,1481,1483],{},[41,1482,207],{"align":43},[41,1484,210],{"align":43},[55,1486,1487],{},[38,1488,1489,1491],{},[60,1490,62],{"align":43},[60,1492,1493],{"align":43},"17",[13,1495,346],{},[307,1497,1498,1501,1504],{},[310,1499,1500],{},"把“创建订单 + 扣库存”封装成一个操作。",[310,1502,1503],{},"应用层只需一次调用。",[310,1505,1506],{},"可以在过程体内进一步加事务控制，保证一致性。",[300,1508],{},[27,1510,1512],{"id":1511},"_1443-验证用户","14.4.3 验证用户",[334,1514,1516],{"className":366,"code":1515,"language":368,"meta":340,"style":340},"DELIMITER $$\nCREATE PROCEDURE ValidateUser(\n  inputUsername VARCHAR(50),\n  inputPassword VARCHAR(50)\n)\nBEGIN\n  SELECT * FROM Users WHERE Username = inputUsername AND Password = inputPassword;\nEND $$\nDELIMITER ;\n\nCALL ValidateUser('john', '1234');\n",[342,1517,1518,1522,1531,1547,1560,1564,1568,1600,1606,1610,1614],{"__ignoreMap":340},[372,1519,1520],{"class":374,"line":375},[372,1521,379],{"class":378},[372,1523,1524,1526,1528],{"class":374,"line":382},[372,1525,386],{"class":385},[372,1527,389],{"class":385},[372,1529,1530],{"class":378}," ValidateUser(\n",[372,1532,1533,1536,1539,1541,1544],{"class":374,"line":395},[372,1534,1535],{"class":378},"  inputUsername ",[372,1537,1538],{"class":385},"VARCHAR",[372,1540,606],{"class":378},[372,1542,1543],{"class":609},"50",[372,1545,1546],{"class":378},"),\n",[372,1548,1549,1552,1554,1556,1558],{"class":374,"line":401},[372,1550,1551],{"class":378},"  inputPassword ",[372,1553,1538],{"class":385},[372,1555,606],{"class":378},[372,1557,1543],{"class":609},[372,1559,618],{"class":378},[372,1561,1562],{"class":374,"line":416},[372,1563,618],{"class":378},[372,1565,1566],{"class":374,"line":422},[372,1567,398],{"class":385},[372,1569,1570,1572,1574,1576,1579,1581,1584,1586,1589,1591,1594,1597],{"class":374,"line":428},[372,1571,404],{"class":385},[372,1573,407],{"class":385},[372,1575,410],{"class":385},[372,1577,1578],{"class":378}," Users ",[372,1580,636],{"class":385},[372,1582,1583],{"class":378}," Username ",[372,1585,642],{"class":385},[372,1587,1588],{"class":378}," inputUsername ",[372,1590,648],{"class":385},[372,1592,1593],{"class":385}," Password",[372,1595,1596],{"class":385}," =",[372,1598,1599],{"class":378}," inputPassword;\n",[372,1601,1602,1604],{"class":374,"line":437},[372,1603,431],{"class":385},[372,1605,434],{"class":378},[372,1607,1608],{"class":374,"line":966},[372,1609,440],{"class":378},[372,1611,1612],{"class":374,"line":1001},[372,1613,674],{"emptyLinePlaceholder":673},[372,1615,1616,1618,1621,1624,1626,1629],{"class":374,"line":1030},[372,1617,529],{"class":385},[372,1619,1620],{"class":378}," ValidateUser(",[372,1622,1623],{"class":724},"'john'",[372,1625,728],{"class":378},[372,1627,1628],{"class":724},"'1234'",[372,1630,733],{"class":378},[13,1632,315],{},[307,1634,1635,1641,1647],{},[310,1636,1637,1640],{},[342,1638,1639],{},"inputUsername","：用户输入的用户名。",[310,1642,1643,1646],{},[342,1644,1645],{},"inputPassword","：用户输入的密码。",[310,1648,1649],{},"过程体执行查询，返回匹配的用户行。",[13,1651,513],{},[334,1653,1655],{"className":366,"code":1654,"language":368,"meta":340,"style":340},"CALL ValidateUser('john', '1234');\n",[342,1656,1657],{"__ignoreMap":340},[372,1658,1659,1661,1663,1665,1667,1669],{"class":374,"line":375},[372,1660,529],{"class":385},[372,1662,1620],{"class":378},[372,1664,1623],{"class":724},[372,1666,728],{"class":378},[372,1668,1628],{"class":724},[372,1670,733],{"class":378},[13,1672,790],{},[32,1674,1675,1685],{},[35,1676,1677],{},[38,1678,1679,1681,1683],{},[41,1680,270],{"align":43},[41,1682,273],{},[41,1684,276],{},[55,1686,1687],{},[38,1688,1689,1691,1693],{},[60,1690,62],{"align":43},[60,1692,285],{},[60,1694,288],{},[334,1696,1698],{"className":366,"code":1697,"language":368,"meta":340,"style":340},"CALL ValidateUser('john', 'wrong');\n",[342,1699,1700],{"__ignoreMap":340},[372,1701,1702,1704,1706,1708,1710,1713],{"class":374,"line":375},[372,1703,529],{"class":385},[372,1705,1620],{"class":378},[372,1707,1623],{"class":724},[372,1709,728],{"class":378},[372,1711,1712],{"class":724},"'wrong'",[372,1714,733],{"class":378},[13,1716,1717],{},"结果：空结果集，表示用户名或密码错误。",[13,1719,346],{},[307,1721,1722,1725,1728],{},[310,1723,1724],{},"封装登录验证逻辑。",[310,1726,1727],{},"应用层调用即可，不用写 SQL。",[310,1729,1730],{},"实际生产环境应对密码加密存储，不直接存明文。",[300,1732],{},[22,1734,1736],{"id":1735},"_145-存储过程的优点","14.5 存储过程的优点",[13,1738,1739],{},"原笔记列出：",[334,1741,1744],{"className":1742,"code":1743,"language":339,"meta":340},[337],"- Transaction control（事务控制）\n- Enhanced security（增强安全性）：用户调用存储过程而非直接访问表\n- Better performance（更好性能）：存储过程在执行前预编译\n- Reusability（可重用性）\n",[342,1745,1743],{"__ignoreMap":340},[13,1747,1748],{},"扩展注释：",[27,1750,1752],{"id":1751},"_1451-transaction-control事务控制","14.5.1 Transaction control（事务控制）",[334,1754,1756],{"className":366,"code":1755,"language":368,"meta":340,"style":340},"START TRANSACTION;\nUPDATE Accounts SET Balance = Balance - amount WHERE AccountID = fromAccount;\nUPDATE Accounts SET Balance = Balance + amount WHERE AccountID = toAccount;\nCOMMIT;\n",[342,1757,1758,1764,1790,1816],{"__ignoreMap":340},[372,1759,1760,1762],{"class":374,"line":375},[372,1761,1110],{"class":385},[372,1763,787],{"class":378},[372,1765,1766,1768,1770,1772,1774,1776,1778,1780,1782,1784,1786,1788],{"class":374,"line":382},[372,1767,1117],{"class":385},[372,1769,972],{"class":378},[372,1771,975],{"class":385},[372,1773,978],{"class":378},[372,1775,642],{"class":385},[372,1777,978],{"class":378},[372,1779,985],{"class":385},[372,1781,988],{"class":378},[372,1783,636],{"class":385},[372,1785,993],{"class":378},[372,1787,642],{"class":385},[372,1789,998],{"class":378},[372,1791,1792,1794,1796,1798,1800,1802,1804,1806,1808,1810,1812,1814],{"class":374,"line":395},[372,1793,1117],{"class":385},[372,1795,972],{"class":378},[372,1797,975],{"class":385},[372,1799,978],{"class":378},[372,1801,642],{"class":385},[372,1803,978],{"class":378},[372,1805,1016],{"class":385},[372,1807,988],{"class":378},[372,1809,636],{"class":385},[372,1811,993],{"class":378},[372,1813,642],{"class":385},[372,1815,1027],{"class":378},[372,1817,1818,1820],{"class":374,"line":401},[372,1819,1129],{"class":385},[372,1821,787],{"class":378},[307,1823,1824,1827,1830],{},[310,1825,1826],{},"多条 SQL 可以放在同一事务中。",[310,1828,1829],{},"保证要么全部成功，要么全部失败。",[310,1831,1832],{},"银行转账、下订单是典型例子。",[27,1834,1836],{"id":1835},"_1452-enhanced-security增强安全性","14.5.2 Enhanced security（增强安全性）",[334,1838,1840],{"className":366,"code":1839,"language":368,"meta":340,"style":340},"-- 只给用户 EXECUTE 权限，不给表 SELECT 权限\nGRANT EXECUTE ON PROCEDURE ValidateUser TO 'app_user'@'%';\n",[342,1841,1842,1847],{"__ignoreMap":340},[372,1843,1844],{"class":374,"line":375},[372,1845,1846],{"class":523},"-- 只给用户 EXECUTE 权限，不给表 SELECT 权限\n",[372,1848,1849,1852,1855,1858,1860,1863,1866,1869,1872,1875],{"class":374,"line":382},[372,1850,1851],{"class":385},"GRANT",[372,1853,1854],{"class":385}," EXECUTE",[372,1856,1857],{"class":385}," ON",[372,1859,389],{"class":385},[372,1861,1862],{"class":378}," ValidateUser ",[372,1864,1865],{"class":385},"TO",[372,1867,1868],{"class":724}," 'app_user'",[372,1870,1871],{"class":378},"@",[372,1873,1874],{"class":724},"'%'",[372,1876,787],{"class":378},[307,1878,1879,1882,1885],{},[310,1880,1881],{},"用户只能调用存储过程，不能直接查表。",[310,1883,1884],{},"可以隐藏表名、列名和 SQL 逻辑。",[310,1886,1887],{},"减少 SQL 注入风险。",[27,1889,1891],{"id":1890},"_1453-better-performance更好性能","14.5.3 Better performance（更好性能）",[307,1893,1894,1897,1900],{},[310,1895,1896],{},"存储过程在首次执行时编译，之后复用执行计划。",[310,1898,1899],{},"减少网络传输：一次调用，代替多条 SQL。",[310,1901,1902],{},"减少 SQL 解析次数。",[27,1904,1906],{"id":1905},"_1454-reusability可重用性","14.5.4 Reusability（可重用性）",[334,1908,1910],{"className":366,"code":1909,"language":368,"meta":340,"style":340},"CALL TransferMoney(101, 202, 500.00);\nCALL TransferMoney(202, 101, 100.00);\nCALL PlaceOrder(1, 1, 3);\n",[342,1911,1912,1934,1957],{"__ignoreMap":340},[372,1913,1914,1916,1918,1920,1922,1924,1926,1928,1930,1932],{"class":374,"line":375},[372,1915,529],{"class":385},[372,1917,1060],{"class":378},[372,1919,183],{"class":609},[372,1921,728],{"class":378},[372,1923,191],{"class":609},[372,1925,728],{"class":378},[372,1927,1071],{"class":609},[372,1929,1074],{"class":378},[372,1931,1077],{"class":609},[372,1933,733],{"class":378},[372,1935,1936,1938,1940,1942,1944,1946,1948,1951,1953,1955],{"class":374,"line":382},[372,1937,529],{"class":385},[372,1939,1060],{"class":378},[372,1941,191],{"class":609},[372,1943,728],{"class":378},[372,1945,183],{"class":609},[372,1947,728],{"class":378},[372,1949,1950],{"class":609},"100",[372,1952,1074],{"class":378},[372,1954,1077],{"class":609},[372,1956,733],{"class":378},[372,1958,1959,1961,1963,1965,1967,1969,1971,1973],{"class":374,"line":395},[372,1960,529],{"class":385},[372,1962,1431],{"class":378},[372,1964,62],{"class":609},[372,1966,728],{"class":378},[372,1968,62],{"class":609},[372,1970,728],{"class":378},[372,1972,90],{"class":609},[372,1974,733],{"class":378},[307,1976,1977,1980,1983],{},[310,1978,1979],{},"同一段逻辑，多次调用，不同参数。",[310,1981,1982],{},"应用层代码更简洁。",[310,1984,1985],{},"修改逻辑时只需改存储过程。",[300,1987],{},[22,1989,1991],{"id":1990},"_146-常见坑总结","14.6 常见坑总结",[1993,1994,1995,1999,2006,2012,2015,2024,2027,2030,2033,2036],"ol",{},[310,1996,1997,567],{},[342,1998,566],{},[310,2000,2001,2003,2004,458],{},[342,2002,566],{}," 只在当前会话有效，创建完应恢复为 ",[342,2005,453],{},[310,2007,2008,2009,2011],{},"存储过程体内的 ",[342,2010,453],{}," 不会提前结束定义，因为已经改了结束符。",[310,2013,2014],{},"参数类型要和实际数据类型匹配。",[310,2016,2017,2018,2021,2022,458],{},"事务中如果出错，应 ",[342,2019,2020],{},"ROLLBACK","，而不是直接 ",[342,2023,1129],{},[310,2025,2026],{},"存储过程可以包含多条 SQL，但不要写得太长，难以维护。",[310,2028,2029],{},"存储过程在数据库中编译，不同数据库语法差异较大。",[310,2031,2032],{},"生产环境应控制存储过程权限，避免滥用。",[310,2034,2035],{},"密码等敏感数据不应明文存储或比较。",[310,2037,2038],{},"存储过程不适合所有场景，简单查询可以直接写 SQL。",[13,2040,2041],{},"核心记忆点：",[334,2043,2046],{"className":2044,"code":2045,"language":339,"meta":340},[337],"存储过程 = 一组 SQL 语句 + 逻辑，编译后存储在数据库中。\nDELIMITER 用于改变语句结束符，方便定义过程体。\nCALL 过程名(参数) 调用存储过程。\n存储过程可包含事务控制、参数、多条 SQL。\n优点：事务控制、安全性、性能、可重用性。\n",[342,2047,2045],{"__ignoreMap":340},[2049,2050,2051],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":340,"searchDepth":395,"depth":395,"links":2053},[2054,2062,2063,2064,2065,2070,2076],{"id":24,"depth":382,"text":25,"children":2055},[2056,2057,2058,2059,2060,2061],{"id":29,"depth":395,"text":30},{"id":102,"depth":395,"text":103},{"id":163,"depth":395,"text":164},{"id":197,"depth":395,"text":198},{"id":229,"depth":395,"text":230},{"id":260,"depth":395,"text":261},{"id":304,"depth":382,"text":305},{"id":362,"depth":382,"text":363},{"id":577,"depth":382,"text":578},{"id":886,"depth":382,"text":887,"children":2066},[2067,2068,2069],{"id":890,"depth":395,"text":891},{"id":1240,"depth":395,"text":1241},{"id":1511,"depth":395,"text":1512},{"id":1735,"depth":382,"text":1736,"children":2071},[2072,2073,2074,2075],{"id":1751,"depth":395,"text":1752},{"id":1835,"depth":395,"text":1836},{"id":1890,"depth":395,"text":1891},{"id":1905,"depth":395,"text":1906},{"id":1990,"depth":382,"text":1991},"学习",null,"MySQL 学习笔记第十一篇：存储过程（Stored Procedures）的概念、创建与调用、带参数的写法，以及优缺点。","md",false,{},"\u002Fposts\u002Fmysql\u002Fmysql-learning-11",{"title":5,"description":2079},"SQL 学习笔记","posts\u002Fmysql\u002Fmysql-learning-11",[2088,2089,2090],"SQL","MySQL","学习笔记","qIIZm5Fule2d4XJaIOH8RBOLUyXpsozKoSXE2Xg7cfg",[2093,2100,2106,2112,2118,2124,2130,2136,2142,2148,2154,2160,2166,2172,2177,2182,2187,2192,2197,2202,2207,2212,2217,2222,2227,2232,2237,2242,2247,2253,2257,2261,2265,2269,2273,2277,2281,2285,2289,2293,2297,2303,2307,2311,2315,2319,2323,2327,2331,2335,2336,2341,2345,2349,2354,2359,2365,2369,2374,2378],{"path":2094,"title":2095,"description":2096,"date":2097,"series":2098,"seriesOrder":2099},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-28","软件分析与设计笔记 28：draw.io 作图手册：五张图的符号位置、落笔顺序与自检","不模拟软件界面，只讲清三件事：每个 UML 符号在 draw.io 里搜什么词、一张图该按什么顺序落笔、画完拿什么标准自查。覆盖用例图、活动图、顺序图、类图、界面草图五种图纸，末尾附「最容易画反的十二笔」总检表。","2026-11-19","软件分析与设计笔记",28,{"path":2101,"title":2102,"description":2103,"date":2104,"series":2098,"seriesOrder":2105},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-27","软件分析与设计笔记 27：Paper 1 应试：从读案例到交卷的一次完整演算","拿一份完整的案例，从头到尾走一遍：怎么读案例列角色、怎么在四个小问之间分配时间、每一张图从哪一句话推出来、两张表怎么填、交卷前拿什么清单自查。附 C25 真题与样卷的题型分值对照。","2026-11-18",27,{"path":2107,"title":2108,"description":2109,"date":2110,"series":2098,"seriesOrder":2111},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-26","软件分析与设计笔记 26：总结：把整条链路串起来","系列收尾：从需求到代码的完整链路回顾、每一环的产出与验收方式、六条贯穿全系列的原则，以及一份可以直接拿去做项目自查的清单。","2026-11-17",26,{"path":2113,"title":2114,"description":2115,"date":2116,"series":2098,"seriesOrder":2117},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-25","软件分析与设计笔记 25：代码重构：坏味道与手法","重构不改变外部行为、只改善内部结构：四种常见坏味道与对应手法、为什么动手之前必须先有测试、什么时候该重构，以及重构和前面那些设计模型是什么关系。","2026-11-16",25,{"path":2119,"title":2120,"description":2121,"date":2122,"series":2098,"seriesOrder":2123},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-24","软件分析与设计笔记 24：设计原则：内聚、耦合与多态","好设计怎么衡量：内聚与耦合分别在说什么、一次从「差设计」到「多态重设计」的完整对照、抽象类与接口的选择依据，以及复用为什么会从优势变成风险。","2026-11-15",24,{"path":2125,"title":2126,"description":2127,"date":2128,"series":2098,"seriesOrder":2129},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-23","软件分析与设计笔记 23：设计建模：从对象模型到能实现的方案","分析模型能用但还不能直接开工：对象模型怎么映射到关系模型、继承映射的三种方案怎么选、数据访问层与 Data Broker 解决什么问题，以及三层架构与 MVC 的分工。","2026-11-14",23,{"path":2131,"title":2132,"description":2133,"date":2134,"series":2098,"seriesOrder":2135},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-22","软件分析与设计笔记 22：分析建模：边界、实体与控制对象","不经过分析直接写代码会踩什么坑、三种分析对象各管一段、怎么用顺序图完成一次用例实现，以及从顺序图导出类的属性、操作与关系的方法。","2026-11-13",22,{"path":2137,"title":2138,"description":2139,"date":2140,"series":2098,"seriesOrder":2141},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-21","软件分析与设计笔记 21：UML：类图与顺序图","为什么需要一种共同的画法：类符号三格怎么画、五种关系的符号与设计含义、每种关系落到 Java 是什么写法，顺序图的三要素怎么摆；以及这三种最容易找不到的符号在 draw.io 里各搜什么词。","2026-11-12",21,{"path":2143,"title":2144,"description":2145,"date":2146,"series":2098,"seriesOrder":2147},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-20","软件分析与设计笔记 20：面向对象概念：对象、关系与多态","面向对象不是「用某个语言写代码」：对象与类的区别、封装真正在保护什么、五种关系（依赖、关联、聚合、组合、继承）各自何时用，以及多态为什么能减少条件判断。","2026-11-11",20,{"path":2149,"title":2150,"description":2151,"date":2152,"series":2098,"seriesOrder":2153},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-19","软件分析与设计笔记 19：规范化与逻辑数据模型","一次改地址引发的数据不一致，讲清规范化的真正动机；UNF 到 3NF 每一步在解决什么问题，派生字段为什么不能存，优化为什么和规范化方向相反，以及逻辑数据模型在 3NF 之上补了什么。","2026-11-10",19,{"path":2155,"title":2156,"description":2157,"date":2158,"series":2098,"seriesOrder":2159},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-18","软件分析与设计笔记 18：数据库分析：实体、关系与数据字典","为什么不能拿到需求就动手建表：实体与关系怎么判定、基数为什么必须由业务方确认、数据命名要遵守什么规则，以及数据字典四列里最容易被空着的那一列。","2026-11-09",18,{"path":2161,"title":2162,"description":2163,"date":2164,"series":2098,"seriesOrder":2165},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-17","软件分析与设计笔记 17：用生成式 AI 做设计与开发：能用的场景与要防的坑","生成式 AI 在工作流里真正站得住的三个场景 —— 造示例数据、解释陌生代码、起草初稿；它为什么会复现社会偏见、为什么会编得像真的，以及哪些内容绝不能直接采用。","2026-11-08",17,{"path":2167,"title":2168,"description":2169,"date":2170,"series":2098,"seriesOrder":2171},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-16","软件分析与设计笔记 16：从 persona 到 URS：一份交付物的完整骨架","把前面十几篇串成一份能交出去的东西：用户需求规格说明书的九个部分、每一章来自哪个环节、由谁确认，以及面向决策者的提案该讲清哪三件事。","2026-11-07",16,{"path":2173,"title":2174,"description":2175,"date":2176,"series":2098,"seriesOrder":1055},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-15","软件分析与设计笔记 15：非功能需求：把「快、稳、安全」变成数字","为什么「系统要快」不是一条需求：非功能需求怎么量化，决定性能的用户量、交易量、数据量三张表怎么估，安全需求要写清什么，以及可靠性的「不可用」该怎么定义。","2026-11-06",{"path":2178,"title":2179,"description":2180,"date":2181,"series":2098,"seriesOrder":1050},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-14","软件分析与设计笔记 14：软件测试：层级、方法与等价类划分","「我们测过了」这句话问题在哪：为什么静态评审和动态测试缺一不可，五个测试层级各自验证什么，白盒与黑盒的差别，以及用等价类划分和边界值把无限输入变成有限用例的方法。","2026-11-05",{"path":2183,"title":2184,"description":2185,"date":2186,"series":2098,"seriesOrder":1045},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-13","软件分析与设计笔记 13：需求获取：五种调查方法与一场访谈","需求从哪里来：五种事实调查方法各自适合回答什么、代价是什么，访谈从准备到记录的全过程，问卷设计里最容易犯的错，以及访谈记录必须写下来的那几项信息。","2026-11-04",{"path":2188,"title":2189,"description":2190,"date":2191,"series":2098,"seriesOrder":1038},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-12","软件分析与设计笔记 12：前端设计：把流程变成屏幕","界面设计不是「画得好看」：从活动图走到屏幕的三步，CRUD 任务流怎么当检查表，界面必须回答的三个问题，信息架构与五种导航类型，视觉一致性与留白，以及移动端与 Web 端的差别。","2026-11-03",{"path":2193,"title":2194,"description":2195,"date":2196,"series":2098,"seriesOrder":1030},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-11","软件分析与设计笔记 11：Agentic AI 与业务流程自动化","AI agent 到底是什么：五项能力与四个核心组件，什么场景该用定时任务、什么该用程序、什么才值得用 agent，两种实现路径的差别，必须提前防住的四类失败模式，以及设计方法从提示词到编排的演进。","2026-11-02",{"path":2198,"title":2199,"description":2200,"date":2201,"series":2098,"seriesOrder":1001},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-10","软件分析与设计笔记 10：精炼用例模型：include、extend 与泛化","三种精炼用例关系的判断标准：include 什么时候用、它和 extend 的区别到底在哪、参与者泛化与用例泛化各解决什么问题，以及精炼到什么程度就该停手。","2026-11-01",{"path":2203,"title":2204,"description":2205,"date":2206,"series":2098,"seriesOrder":966},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-09","软件分析与设计笔记 09：功能需求（二）：从流程到业务实体","活动图画完之后，怎么从流程里筛出真正的业务实体：三条判断标准、哪些名词其实不该建实体、属性从哪里来，以及领域模型和后面的数据模型分别负责什么。","2026-10-31",{"path":2208,"title":2209,"description":2210,"date":2211,"series":2098,"seriesOrder":437},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-08","软件分析与设计笔记 08：功能需求（一）：用例模型与活动图","从用户故事到用例的翻译过程：什么才算「好」的参与者，系统边界怎么划，用例描述必须写清哪四件事，活动图怎么把分支、并行与泳道画清楚，以及这两个图在 draw.io 里各用哪个形状、最容易画错哪一笔。","2026-10-30",{"path":2213,"title":2214,"description":2215,"date":2216,"series":2098,"seriesOrder":428},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-07","软件分析与设计笔记 07：用户故事：把需求写成可验收的一句话","用户故事的三段结构，怎么从用户画像和数据地图里把它提取出来，一条故事多大算合适、时序为什么也是需求的一部分，以及验收标准怎么写才算真的可验收。","2026-10-29",{"path":2218,"title":2219,"description":2220,"date":2221,"series":2098,"seriesOrder":422},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-06","软件分析与设计笔记 06：范围界定（数据侧）：用数据校验你的判断","访谈结论和数字对不上时该怎么办：分析的四个层次分别在回答什么问题、为什么不能跳级、数据准备与搜集怎么做成一张应用地图，以及数据最容易在哪几个地方骗你。","2026-10-28",{"path":2223,"title":2224,"description":2225,"date":2226,"series":2098,"seriesOrder":416},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-05","软件分析与设计笔记 05：范围界定（用户侧）：用户画像与客户旅程图","为什么「所有用户」是一个陷阱，项目边界该怎么划，用户画像要写哪五个要素、最容易写成什么废纸，客户旅程图怎么把阶段、想法和情绪画在同一张纸上，以及怎么从这两样东西一步步推到可验证的需求。","2026-10-27",{"path":2228,"title":2229,"description":2230,"date":2231,"series":2098,"seriesOrder":401},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-04","软件分析与设计笔记 04：接手陌生案例的第一步：三问与一张表","拿到一个完全陌生的业务案例，怎么从零开始不跑偏：只问 Who、What、Why 三个问题，把「问题—事实—假设—理由」记成一张四列表，说清为什么假设必须写明理由，以及带着问题进访谈为什么比空手去有效得多。","2026-10-26",{"path":2233,"title":2234,"description":2235,"date":2236,"series":2098,"seriesOrder":395},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-03","软件分析与设计笔记 03：Agile 与 Scrum：迭代、角色与产出物","从「三周做完交付，客户说这不是我要的」讲起，说清瀑布式与迭代式的根本差别，Scrum 的三个角色和三种产出物，一个 sprint 里四次固定活动各自在解决什么问题，以及敏捷里最常见的几个误解。","2026-10-25",{"path":2238,"title":2239,"description":2240,"date":2241,"series":2098,"seriesOrder":382},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-02","软件分析与设计笔记 02：设计思维、用户驱动与数据驱动","从「客服要一个能一次看完所有排期的界面」这个翻车案例讲起，说清设计思维到底是什么、用户驱动与数据驱动各自能看见什么又看不见什么、为什么真正的落点是把两者合起来的 data-informed design，以及访谈中怎么问出有用的信息。","2026-10-24",{"path":2243,"title":2244,"description":2245,"date":2246,"series":2098,"seriesOrder":375},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-01","软件分析与设计笔记 01：课程地图与从需求到设计的主干","这一篇不写具体方法，先把整条链路铺开：软件分析与设计到底要解决什么问题，从需求到设计分成哪两段，中间有哪十几个环节，一份完整的交付物里应该有什么，以及后面 27 篇笔记怎么排、新人该按什么顺序读。","2026-10-23",{"path":2248,"title":2249,"description":2250,"date":2251,"series":2252,"seriesOrder":375},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-01","Spring 学习笔记 01：IoC、依赖注入与第一个 Spring Boot 应用","从「手写 new 出来的三层结构」的痛点讲起，说清 IoC 容器、Bean 与依赖注入是怎么回事，再用 Maven 与 Spring Initializr 跑起第一个能返回 JSON 的应用。","2026-10-15","Spring 学习笔记",{"path":2254,"title":2255,"description":2256,"date":2251,"series":2252,"seriesOrder":382},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-02","Spring 学习笔记 02：Spring Data JPA 基础——实体映射与 Repository","用 ORM 把类映射到表：@Entity、@Id、@GeneratedValue 怎么用，CrudRepository 三层接口怎么选，H2 与 MySQL 怎么配，以及 ddl-auto 到底该写哪个值。",{"path":2258,"title":2259,"description":2260,"date":2251,"series":2252,"seriesOrder":395},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-03","Spring 学习笔记 03：JPA 查询——派生方法、JPQL 与分页排序","Repository 自带的方法只够按主键查，这篇讲三种自己造查询的方式：方法名派生、@Query 写 JPQL、原生 SQL，以及 Pageable 分页排序和投影返回。",{"path":2262,"title":2263,"description":2264,"date":2251,"series":2252,"seriesOrder":401},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-04","Spring 学习笔记 04：JPA 关联映射与仓储测试","一对多、多对多怎么映射，谁负责维护外键，FetchType 与 CascadeType 该怎么选，顺带用 @DataJpaTest 与 AssertJ 把仓储层测起来。",{"path":2266,"title":2267,"description":2268,"date":2251,"series":2252,"seriesOrder":416},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-05","Spring 学习笔记 05：Spring MVC 与 Thymeleaf 入门","一次页面请求在 Spring MVC 里走了哪几步，@Controller 怎么把数据交给模板，@PathVariable 与 @RequestParam 怎么取参数，以及控制器为什么必须是无状态的。",{"path":2270,"title":2271,"description":2272,"date":2251,"series":2252,"seriesOrder":422},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-06","Spring 学习笔记 06：表单绑定与服务端校验","把表单字段绑到对象上，用 Bean Validation 注解做服务端校验，把错误信息显示回用户刚填的那一页，再补上自定义校验注解与全局异常处理。",{"path":2274,"title":2275,"description":2276,"date":2251,"series":2252,"seriesOrder":428},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-07","Spring 学习笔记 07：Thymeleaf 片段复用与国际化","用 th:fragment 与 th:insert\u002Fth:replace 把导航栏、页脚抽成片段，页面套用统一布局，再用 messages.properties 与 LocaleResolver 做中英文切换。",{"path":2278,"title":2279,"description":2280,"date":2251,"series":2252,"seriesOrder":437},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-08","Spring 学习笔记 08：HTTP Session 与会话状态管理","HTTP 无状态带来的问题，Cookie 与 Session 怎么分工，Spring 里读写会话的几种方式，以及会话固定、超时、多实例共享这些必须想清楚的事。",{"path":2282,"title":2283,"description":2284,"date":2251,"series":2252,"seriesOrder":966},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-09","Spring 学习笔记 09：拦截器 HandlerInterceptor","登录检查、权限判断、请求耗时统计这类横切逻辑该放哪：HandlerInterceptor 的三个回调各在什么时机执行，怎么注册与排除路径，以及它和过滤器的分工。",{"path":2286,"title":2287,"description":2288,"date":2251,"series":2252,"seriesOrder":1001},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-10","Spring 学习笔记 10：Service 层与事务","一组数据库操作要么全成功要么全失败，靠 @Transactional 怎么做到；传播行为、隔离级别、回滚规则怎么选，以及自调用失效、乐观锁悲观锁这些实战里的坑。",{"path":2290,"title":2291,"description":2292,"date":2251,"series":2252,"seriesOrder":1030},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-11","Spring 学习笔记 11：REST API 基础——把 Service 暴露成接口","同一套 Service 怎么同时服务页面和客户端：@RestController 的路由与方法语义、状态码怎么给、DTO 怎么定，以及 MockMvc 端到端和切片测试的两种写法与各自的坑。",{"path":2294,"title":2295,"description":2296,"date":2251,"series":2252,"seriesOrder":1038},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-12","Spring 学习笔记 12：REST 进阶——版本、更新、跨域与文档化","接口能跑通之后要处理的几件事：URL 与响应结构怎么定、版本怎么演进、更新怎么做到可安全重试、CORS 怎么配、HATEOAS 与 Spring Data REST 该不该用。",{"path":2298,"title":2299,"description":2300,"date":2301,"series":2302,"seriesOrder":401},"\u002Fposts\u002Fjava\u002Fjava-functionalprogramming-notes","Java 函数式编程笔记：Lambda、函数式接口与 Streams","学习 Java 函数式编程时的个人笔记：Lambda 表达式的五步简写与方法引用、常见函数式接口、Stream 流水线与惰性求值，以及创建流、中间操作、终止操作与收集器的用法。示例代码均为自写，运行结果来自本地实测。","2026-10-13","Java 学习笔记",{"path":2304,"title":2305,"description":2306,"date":2301,"series":2302,"seriesOrder":395},"\u002Fposts\u002Fjava\u002Fjava-generics-notes","Java 泛型笔记：泛型方法、有界类型与泛型类","学习 Java 泛型时的个人笔记：从重复的重载方法与多态尝试讲起，再到泛型方法与类型推断、有界泛型类型、Comparable 与 Number 边界，以及泛型类在集合中的用法。示例代码均为自写，运行结果来自本地实测。",{"path":2308,"title":2309,"description":2310,"date":1472,"series":2085,"seriesOrder":401},"\u002Fposts\u002Fmysql\u002Fmysql-learning-04","MySQL 学习笔记 04：数据操纵（增删改）","MySQL 学习笔记第四篇：INSERT 插入（含批量插入与 INSERT ... SELECT）、UPDATE 更新、DELETE 删除，以及各自的坑。",{"path":2312,"title":2313,"description":2314,"date":1472,"series":2085,"seriesOrder":416},"\u002Fposts\u002Fmysql\u002Fmysql-learning-05","MySQL 学习笔记 05：DDL 概览","MySQL 学习笔记第五篇：数据定义语言概览——Schema、Table、View、Index、Constraints 这几类对象分别对应哪些 CREATE \u002F ALTER \u002F DROP 命令。",{"path":2316,"title":2317,"description":2318,"date":1472,"series":2085,"seriesOrder":422},"\u002Fposts\u002Fmysql\u002Fmysql-learning-06","MySQL 学习笔记 06：CREATE TABLE 与数据类型","MySQL 学习笔记第六篇：CREATE TABLE 的完整语法、常用数据类型的选型建议，以及外键的两种定义方式。",{"path":2320,"title":2321,"description":2322,"date":1472,"series":2085,"seriesOrder":428},"\u002Fposts\u002Fmysql\u002Fmysql-learning-07","MySQL 学习笔记 07：索引","MySQL 学习笔记第七篇：索引的作用与 B+ 树结构、单列与复合索引的创建、索引的代价与适用场景。",{"path":2324,"title":2325,"description":2326,"date":1472,"series":2085,"seriesOrder":437},"\u002Fposts\u002Fmysql\u002Fmysql-learning-08","MySQL 学习笔记 08：DROP 与 ALTER","MySQL 学习笔记第八篇：DROP 删除数据库对象、ALTER 修改表结构，以及常见的坑。",{"path":2328,"title":2329,"description":2330,"date":1472,"series":2085,"seriesOrder":966},"\u002Fposts\u002Fmysql\u002Fmysql-learning-09","MySQL 学习笔记 09：约束与数据完整性","MySQL 学习笔记第九篇：SQL 约束——NOT NULL、CHECK、PRIMARY KEY、FOREIGN KEY 等如何保证数据完整性，以及它们的对比。",{"path":2332,"title":2333,"description":2334,"date":1472,"series":2085,"seriesOrder":1001},"\u002Fposts\u002Fmysql\u002Fmysql-learning-10","MySQL 学习笔记 10：视图","MySQL 学习笔记第十篇：视图（Views）的定义与特性、和表的对比、创建与使用，以及可更新视图的限制。",{"path":2083,"title":5,"description":2079,"date":1472,"series":2085,"seriesOrder":1030},{"path":2337,"title":2338,"description":2339,"date":2340,"series":2085,"seriesOrder":375},"\u002Fposts\u002Fmysql\u002Fmysql-learning-01","MySQL 学习笔记 01：MySQL 基础知识","MySQL 学习笔记第一篇：MySQL Server 与 Workbench 的安装，数据库与 DBMS 的基本概念，关系模型、ACID 事务、常见数据类型，以及后续的学习路线。","2026-10-09",{"path":2342,"title":2343,"description":2344,"date":2340,"series":2085,"seriesOrder":382},"\u002Fposts\u002Fmysql\u002Fmysql-learning-02","MySQL 学习笔记 02：SELECT 查询与过滤","MySQL 学习笔记第二篇：SELECT 的基本语法与逻辑执行顺序，WHERE 过滤（比较、LIKE、IN、BETWEEN、NULL）、各种 JOIN、UNION 与子查询。",{"path":2346,"title":2347,"description":2348,"date":2340,"series":2085,"seriesOrder":395},"\u002Fposts\u002Fmysql\u002Fmysql-learning-03","MySQL 学习笔记 03：聚合、分组与输出控制","MySQL 学习笔记第三篇：COUNT\u002FSUM\u002FAVG 等聚合函数、GROUP BY 分组、HAVING 过滤、ORDER BY 与 LIMIT，以及一条 SELECT 的完整逻辑执行顺序。",{"path":2350,"title":2351,"description":2352,"date":2353,"series":2302,"seriesOrder":382},"\u002Fposts\u002Fjava\u002Fjava-oop-notes","Java 面向对象笔记：类、继承、多态与异常","学习 Java 面向对象编程时的个人笔记：类与对象、封装与访问修饰符、继承与多态、集合、抽象类与接口、异常处理。示例代码均为自写，运行结果来自本地实测。","2026-10-06",{"path":2355,"title":2356,"description":2357,"date":2358,"series":2302,"seriesOrder":375},"\u002Fposts\u002Fjava\u002Fjava-basics-notes","Java 基础语法笔记：从环境安装到方法与数组","学习 Java 基础语法时的个人笔记：JDK 安装、输入输出、变量与运算符、条件与循环、字符串、数组与方法。示例代码均为自写，运行结果来自本地实测。","2026-10-05",{"path":2360,"title":2361,"description":2362,"date":2363,"series":2364,"seriesOrder":375},"\u002Fposts\u002Fgit\u002Fgit-install-and-basics","Git 安装与基本使用","从装好 Git 到跑通日常提交流程，把最常用的几条命令和它们背后的三个区域讲清楚。","2026-10-04","Git 入门",{"path":2366,"title":2367,"description":2368,"date":2363,"series":2364,"seriesOrder":382},"\u002Fposts\u002Fgit\u002Flocal-remote-repository","本地远程仓库的部署","用自己的机器当 Git 服务器：建一个裸仓库、走 SSH 连上去，把本地提交推上去。",{"path":2370,"title":2371,"description":2372,"date":2363,"series":2373,"seriesOrder":375},"\u002Fposts\u002Fguide\u002Fguide-file-layout","文件都放在哪","文章、图片、封面分别放在仓库的什么位置，哪些目录可以放手改、哪些碰了会出问题。","博客使用指南",{"path":2375,"title":2376,"description":2377,"date":2363,"series":2373,"seriesOrder":382},"\u002Fposts\u002Fguide\u002Fguide-frontmatter","frontmatter 每个字段是什么意思","文章开头那段被横线包住的字段，逐个说明作用、默认行为和写错的后果。",{"path":2379,"title":2380,"description":2381,"date":2363,"series":2373,"seriesOrder":395},"\u002Fposts\u002Fguide\u002Fguide-markdown","正文怎么写 —— Markdown 与排版效果","标题、链接、图片、代码、表格分别怎么写，以及本站对它们做了哪些自定义处理。",1793268186361]