久久亚洲成a人片熟女精品色一区二区三区|国产精品视频第一精品视频|av天堂热无码手机版|亚洲?v无码久久无遮挡|国产精品偷伦视频免费观看国产|麻豆国产自产精品丰满熟妇|av无码av不卡一区二区|久久亚洲精品中文字

ARTICLE DETAIL

資訊詳情

深耕商務(wù)建站與企業(yè)官網(wǎng)運營的一線實戰(zhàn)洞察。

Hibernate N+1問題全解析:從原理到實戰(zhàn),徹底告別SQL爆炸

Hibernate N+1問題全解析:從原理到實戰(zhàn),徹底告別SQL爆炸 先說一個真實場景。某天深夜我負(fù)責(zé)的服務(wù)突然被數(shù)據(jù)庫CPU告警刷屏上去一看一條列表接口在高峰期每秒打出幾百條一模一樣的SQL骨架只是IN參數(shù)不同。翻代碼就是Hibernate的默認(rèn)懶加載配上循環(huán)遍歷把訂單查出來后逐條去get買家、get商品、get收貨地址。當(dāng)時我就意識到N1這個問題不踩一次永遠(yuǎn)記不牢踩過一次就會在寫每一個關(guān)聯(lián)映射之前先問自己三遍這里到底會發(fā)幾條SQL這篇文章我把Hibernate里N1問題的來龍去脈、排查套路、常用解法和實戰(zhàn)取舍一次性講透。適合正在用JPA/Hibernate做持久層開發(fā)的同學(xué)尤其適合被列表接口性能問題折磨過、或者面試前想把這個點徹底搞清楚的朋友。文中所有示例我都基于一個模擬電商項目X的訂單域模型來講代碼風(fēng)格貼近日常業(yè)務(wù)開發(fā)不是教科書版簡化Demo。1. N1問題的本質(zhì)不是查詢慢而是查詢次數(shù)失控1.1 一個最簡單的復(fù)現(xiàn)場景先定義兩個實體。訂單OrderEntity和訂單項OrderItemEntity一對多關(guān)系這是電商系統(tǒng)里最常見的模型。Entity Table(name t_order) public class OrderEntity { Id private Long id; private String orderNo; private Long buyerId; OneToMany(mappedBy order) private ListOrderItemEntity items new ArrayList(); } Entity Table(name t_order_item) public class OrderItemEntity { Id private Long id; private Long productId; private Integer quantity; ManyToOne(fetch FetchType.LAZY) JoinColumn(name order_id) private OrderEntity order; }需求很簡單在訂單列表頁展示10個訂單同時要顯示每個訂單里有多少個商品項。于是代碼這樣寫ListOrderEntity orders orderRepository.findLatestOrders(10); for (OrderEntity order : orders) { int itemCount order.getItems().size(); ... }只要items上的抓取策略是默認(rèn)的LAZY這10個訂單就會觸發(fā)10次額外的select * from t_order_item where order_id ?。再加上最開始的1次訂單查詢一共是1 10 11條SQL。列表只有10條數(shù)據(jù)體感不明顯但當(dāng)你把列表改成100條、每頁50條時就是1 100條SQL其中99條都是重復(fù)結(jié)構(gòu)。這就是N1的經(jīng)典形態(tài)一次查詢得到N條主數(shù)據(jù)隨后逐條訪問關(guān)聯(lián)屬性又發(fā)出N條查詢。N越大問題越猙獰而且它不會在開發(fā)環(huán)境暴露因為開發(fā)時數(shù)據(jù)量少性能差異完全被網(wǎng)絡(luò)延遲和本地數(shù)據(jù)庫的快速響應(yīng)掩蓋了。1.2 為什么Hibernate特別容易踩中這個坑很多從MyBatis轉(zhuǎn)過來的同學(xué)覺得奇怪我寫個關(guān)聯(lián)查詢把結(jié)果一次性取出來不就行了嗎問題的根源在于Hibernate的實體加載模型。Hibernate引入了“持久化上下文”和“代理對象”這套機制。懶加載關(guān)聯(lián)屬性時返回的不是真實集合而是一個被委托的PersistentBag。你對這個集合調(diào)用size()、iterator()、contains()時Hibernate才臨時決定哦還沒初始化我發(fā)一條SQL現(xiàn)取。也就是說N1不是Hibernate的bug而是懶加載機制在“循環(huán)遍歷”這種代碼形態(tài)下的必然產(chǎn)物。我之前在某公司排查過一個線上問題代碼里只是把List 的查詢結(jié)果做了stream().map(Order::getItems).count()這類操作結(jié)果SQL數(shù)量直接爆炸。表面上看是框架的問題實際是開發(fā)者對“什么時候觸發(fā)查詢”完全無感。Hibernate把這條邊界藏得太深你寫了order.getItems()根本看不出來這里會打DB。另一個容易忽略的點是事務(wù)邊界。只要Session還開著懶加載隨時可能觸發(fā)額外查詢Session關(guān)了再訪問直接拋LazyInitializationException。很多人為了解決這個異常粗暴地改成EAGER這就把N1問題從“可能發(fā)生”變成了“必然發(fā)生”。后面會詳細(xì)講為什么EAGER是飲鴆止渴。1.3 怎么量化你到底發(fā)了多少條SQL不把SQL數(shù)量量化優(yōu)化就無從談起。我有兩個常用手段都是低成本高收益的。先看Hibernate自己的日志。在application.yml里把show_sql和format_sql打開同時把日志級別調(diào)到DEBUG或者TRACEspring: jpa: show-sql: true properties: hibernate.format_sql: true hibernate.use_sql_comments: true其中use_sql_comments非常有用它會在生成的SQL里附帶一段Hibernate內(nèi)部注釋標(biāo)注這條SQL是哪一行Java代碼觸發(fā)的。線上排查時能直接定位到service層的某個方法省去逐行打斷點的麻煩。不過show_sql輸出到控制臺有時候會混在業(yè)務(wù)日志里很難看我在正式環(huán)境會引入數(shù)據(jù)源代理工具比如p6spy。它的思路是攔截所有JDBC執(zhí)行統(tǒng)一格式化打印還能統(tǒng)計每條SQL的耗時。配置很簡單引入依賴后替換driverClassName和jdbcUrl前綴就行。p6spy還支持自定義LogMessageFormat把SQL和執(zhí)行時間拼在一起排查N1時非常直觀。還有一個偏門但很好用的辦法查數(shù)據(jù)庫端的連接會話數(shù)。如果某個接口在短時間內(nèi)對同一張表發(fā)起了幾十條結(jié)構(gòu)相似的查詢數(shù)據(jù)庫的information_schema.processlist里能看到一長串同類SQL。這也是快速確認(rèn)N1是否存在的鐵證。2. 先搞清楚映射設(shè)計再談優(yōu)化關(guān)聯(lián)關(guān)系是怎么變成SQL的2.1 FetchType的默認(rèn)值是個陷阱很多開發(fā)者對ManyToOne、OneToOne、OneToMany、ManyToMany四種關(guān)聯(lián)的默認(rèn)fetch策略記不準(zhǔn)確這里我把它整理成一張表關(guān)聯(lián)類型默認(rèn)Fetch策略默認(rèn)情況下訪問關(guān)聯(lián)屬性時的行為ManyToOneEAGER主查詢時立即JOIN查詢OneToOneEAGER主查詢時立即JOIN查詢OneToManyLAZY首次訪問集合時額外查詢ManyToManyLAZY首次訪問集合時額外查詢看到?jīng)]有ManyToOne默認(rèn)是立即抓取也就是說你查訂單列表時訂單上那個ManyToOne的買家對象已經(jīng)跟著一起查出來了。這不是N1但它會帶來另一個問題多余的、可能根本用不到的大字段關(guān)聯(lián)被強行加載。更糟糕的是如果多個ManyToOne互相指來指去Hibernate生成SQL時可能產(chǎn)生笛卡爾積式的多表JOIN行數(shù)膨脹非常嚇人。所以我個人的鐵律是無論哪種關(guān)聯(lián)一律顯式寫成fetch FetchType.LAZY。好處有兩個。第一避免默認(rèn)EAGER導(dǎo)致的無謂查詢和超大JOIN第二把“什么時候加載關(guān)聯(lián)數(shù)據(jù)”的決定權(quán)從映射配置移交到業(yè)務(wù)代碼中配合JOIN FETCH或者EntityGraph精確控制思路才清晰。你可能擔(dān)心全改成LAZY之后某些地方訪問關(guān)聯(lián)屬性會報LazyInitializationException。這個問題的正確解法不是改回EAGER而是保證關(guān)聯(lián)屬性在事務(wù)范圍內(nèi)被初始化或者用實體圖明確指定要查詢的關(guān)聯(lián)。這點放在后面方案部分細(xì)說。2.2 集合委托機制為什么getItems().size()不觸發(fā)查詢這里有個很有意思的細(xì)節(jié)。對于被懶加載的集合屬性Hibernate返回的不是ArrayList而是PersistentBag。這個類繼承了AbstractCollection但它內(nèi)部維護了一個尚未初始化的狀態(tài)。當(dāng)你調(diào)用size()時它可能不需要訪問數(shù)據(jù)庫——因為無論集合里有多少元素一個空的或者未初始化的PersistentBag的size()都可以憑內(nèi)部的queuedOperations判斷。但當(dāng)你調(diào)用iterator()、toArray()或者stream()時它就必須確定真實數(shù)據(jù)從而觸發(fā)初始化SQL。這個機制解釋了為什么有些代碼看起來遍歷了集合卻沒發(fā)SQL另一些代碼明明只是取個數(shù)卻發(fā)了SQL。這也引出一個優(yōu)化小技巧如果你只需要判斷集合是否為空優(yōu)先用!order.getItems().isEmpty()而不是order.getItems().size() 0在某些情況下能少接一次數(shù)據(jù)庫往返。不過這只是錦上添花真正解決N1還是得靠下面的方案。理解了委托機制后你再看N1問題本質(zhì)上就是“代碼觸發(fā)了一個又一個集合/實體的初始化請求”。優(yōu)化的方向其實就兩條路要么一次性把需要的關(guān)聯(lián)數(shù)據(jù)查出來讓初始化不發(fā)生在循環(huán)里要么減少初始化發(fā)生的次數(shù)延遲到批量場景統(tǒng)一處理。2.3 事務(wù)邊界是放大器同一個代碼塊事務(wù)邊界不同N1的嚴(yán)重程度完全不同。如果循環(huán)訪問集合屬性的代碼在一個長事務(wù)里那么N次額外查詢都發(fā)生在同一條數(shù)據(jù)庫連接上占用連接時間變長連接池壓力驟增最終影響整個應(yīng)用的吞吐。更隱蔽的是如果配合了讀寫分離一次請求里發(fā)出的每一條SQL都可能被路由到不同的只讀副本N1會讓各副本之間的負(fù)載嚴(yán)重不均。事務(wù)還會放大另一個問題延遲加載不只是查詢還可能是數(shù)據(jù)不一致。比如你先查出訂單列表事務(wù)中途其他線程修改了某個訂單的items數(shù)據(jù)你再訪問該訂單的items集合時Hibernate發(fā)出的查詢可能拿到不同的數(shù)據(jù)造成同一個事務(wù)內(nèi)讀到“前后不一致”的結(jié)果。這也是我建議在事務(wù)邊界內(nèi)盡早把關(guān)聯(lián)數(shù)據(jù)初始化完整的原因之一。3. 解決方案實戰(zhàn)對比JOIN FETCH、實體圖和批量抓取3.1 第一板斧JOIN FETCH先看最直觀的方案用JPQL寫顯式關(guān)聯(lián)抓取Query(value select distinct o from OrderEntity o left join fetch o.items order by o.id desc) ListOrderEntity findOrdersWithItems();這條JPQL翻譯成SQL后會一次性把訂單和訂單項通過LEFT JOIN查出來Hibernate根據(jù)結(jié)果集里的OrderEntity主鍵去重把items正確組裝到每個訂單上。最終SQL只有1條N1直接消失。JOIN FETCH的適用場景非常廣但它有幾個必須知道的注意事項。第一分頁問題。left join fetch一對多關(guān)聯(lián)時如果同時使用setFirstResult/setMaxResultsHibernate會直接在內(nèi)存中分頁而不是走數(shù)據(jù)庫的LIMIT。因為結(jié)果集的行數(shù)已經(jīng)被關(guān)聯(lián)表放大數(shù)據(jù)庫層面的分頁語義會錯亂。如果你在訂單列表頁既要一次性查出items又要分頁JOIN FETCH并不合適這種情況我后文會講替代方案。第二distinct的去重語義。不加distinct如果關(guān)聯(lián)子表有多條記錄同一個訂單實體在結(jié)果集里會出現(xiàn)多行Hibernate雖然能根據(jù)主鍵識別為同一實體但如果你用的是普通JPQL返回可能得到重復(fù)的訂單引用。distinct能保證結(jié)果集層面的去重但要注意distinct不是SQL層面的行去重而是Hibernate實體去重的語義。第三多級關(guān)聯(lián)。如果訂單還要關(guān)聯(lián)買家信息可以繼續(xù)在JPQL里加left join fetch o.buyer。但如果同時fetch兩個集合結(jié)果集行數(shù)會變成兩個集合行數(shù)的笛卡爾積數(shù)據(jù)量立刻膨脹。我的經(jīng)驗是同一查詢中fetch的集合不要超過1個實在要多個集合時優(yōu)先考慮改為分批查詢或二級緩存。3.2 第二板斧EntityGraph更優(yōu)雅的聲明式方案EntityGraph是JPA 2.1引入的規(guī)范Spring Data JPA對它支持得非常好。它的思路和JOIN FETCH類似都是通過一條SQL把關(guān)聯(lián)數(shù)據(jù)join出來但它是聲明式的不需要寫JPQL對代碼侵入更小。EntityGraph(attributePaths {items}) Query(select o from OrderEntity o order by o.id) ListOrderEntity findOrdersWithItems();也可以在Repository方法名上直接加EntityGraph或者定義在實體類上。用起來非常靈活。它和JOIN FETCH的基本原理一致我在實際項目中通常優(yōu)先用EntityGraph原因是維護成本低避免一堆JPQL字符串散落在代碼里。EntityGraph也支持子圖比如同時抓取訂單的itemsitems又關(guān)聯(lián)product可以這么寫EntityGraph(attributePaths {items, items.product})這個場景如果某個產(chǎn)品的ManyToOne默認(rèn)是LAZY那么把product加進attributePaths后會在一條SQL里把兩層關(guān)系全部join出來。但同樣不要濫用子圖。我見過一個同事把整條對象鏈全都塞進attributePaths結(jié)果一條查詢join了五張表返回的數(shù)據(jù)行數(shù)呈笛卡爾積爆炸接口從幾十毫秒變成了幾秒。記住EntityGraph是精確按需加載不是把所有可能用到的關(guān)系都塞進去。還有一個隱藏知識點當(dāng)EntityGraph和實體上的fetch配置沖突時以EntityGraph的配置為準(zhǔn)。所以哪怕你在實體里全設(shè)了LAZY也不影響EntityGraph工作。這也是我推薦全LAZY策略的一個支撐點。3.3 第三板斧BatchSize批量初始化的黑馬有些場景你根本不想動JPQL和Repository只想讓現(xiàn)有的懶加載代碼少發(fā)幾條SQL。這時候BatchSize就派上用場了。Entity Table(name t_order) public class OrderEntity { OneToMany(mappedBy order) BatchSize(size 50) private ListOrderItemEntity items new ArrayList(); }當(dāng)Hibernate檢測到多個未初始化的集合需要加載時它會根據(jù)BatchSize的大小一次性把多個order_id拼到IN條件里select * from t_order_item where order_id in (?, ?, ?, ...)。假設(shè)你有10個訂單的items需要初始化BatchSize50時就會把10個訂單的id放進同一個INSQL從10條變成1條。BatchSize真正厲害的地方在于它不需要改變調(diào)用方代碼。循環(huán)遍歷、stream操作這些代碼原封不動Hibernate在幕后自動幫你合并查詢。對于存量項目改造這是侵入性最小的方案。但要注意BatchSize生效的前提是Hibernate能推斷出“這些集合要一起加載”通常發(fā)生在一次flush/per-rollback的懶加載初始化階段。如果業(yè)務(wù)代碼在每次訪問集合前手動清空了SessionBatchSize就失效了。BatchSize也有一個局限它不會減少訪問集本身時發(fā)起的查詢邏輯只是把N次查詢合并成N/size次。如果N1000BatchSize50仍然有20條SQL。它能極大緩解問題但不能根除。所以更徹底的做法還是讓業(yè)務(wù)代碼不觸發(fā)額外的集合訪問。3.4 第四板斧二級緩存和Cacheable如果N1場景是“同一批訂單的數(shù)據(jù)在短時間內(nèi)被反復(fù)查詢”比如緩存熱點數(shù)據(jù)、配置類數(shù)據(jù)二級緩存可以有效地把重復(fù)SQL攔在應(yīng)用層之外。Spring Data JPA里配合Cacheable或者Hibernate的二級緩存可以讓相同的查詢走緩存不查數(shù)據(jù)庫。二級緩存的坑在于它是弱一致性緩存如果數(shù)據(jù)更新了緩存沒有及時失效就會讀到臟數(shù)據(jù)。所以我一般只把“很少變化、被高頻讀取”的數(shù)據(jù)放進二級緩存比如商品分類、地區(qū)表。訂單這種高頻變更的數(shù)據(jù)強行放二級緩存只會帶來一致性災(zāi)難。另外二級緩存需要你顯式配置CacheConcurrencyStrategy不同策略的讀寫行為完全不同新手很容易用錯。3.5 四種方案的選型建議我把這幾種方案的使用場景歸納成一張表方便你做決策方案最佳場景注意事項JOIN FETCH列表頁需要展示單層關(guān)聯(lián)明細(xì)可接受1條大SQL一對多分頁時謹(jǐn)慎多集合fetch會導(dǎo)致行膨脹EntityGraph多層關(guān)聯(lián)、按需指定查詢路徑代碼可維護性要求高attributePaths不要塞太多按需選擇BatchSize存量代碼改造、無法修改業(yè)務(wù)遍歷邏輯只能減少SQL條數(shù)不能根除批量大小需測試二級緩存高頻只讀、極少變更的數(shù)據(jù)如字典配置注意一致性不適用頻繁寫的數(shù)據(jù)沒有什么銀彈。我見過一些團隊一刀切要求所有列表查詢必須JOIN FETCH結(jié)果分頁接口反而出現(xiàn)內(nèi)存分頁OOM也有人迷信BatchSize結(jié)果寫入場景下集合初始化時機不受控性能依然抖動。正確做法是先看清楚這個接口的IO模式再決定用哪一種。4. 分頁場景下的N1一對多查詢的正確打開方式4.1 為什么JOIN FETCH和分頁水火不容前面埋了一個伏筆現(xiàn)在展開講。訂單列表頁通常需求是這樣的每頁展示20條訂單每條訂單下面要顯示items數(shù)量。用firstResult(page * size)配合JOIN FETCH會出什么問題假設(shè)第2頁的20條訂單每個訂單有5個訂單項。數(shù)據(jù)庫LEFT JOIN后產(chǎn)生的行數(shù)是20×5100行。Hibernate拿到結(jié)果集后要先把100行拆成20個訂單實體再對items賦值。問題是分頁的LIMIT應(yīng)該加在哪個SQL上正確的語義是先找20個訂單再把這些訂單的items查出來。但如果SQL本身是JOIN形態(tài)LIMIT就會被應(yīng)用到100行上你得到的前N行可能只包含幾個完整訂單甚至某個訂單被截斷了一半的items。Hibernate在這條路上的實現(xiàn)選擇是如果它檢測到JPQL中有一對多fetch且有分頁參數(shù)它干脆不做數(shù)據(jù)庫分頁把所有數(shù)據(jù)查出來后在內(nèi)存里做分頁。這個行為在數(shù)據(jù)量大時是致命的——一張百萬行的訂單表你要查第100頁它會把所有訂單及所有items全部加載到堆內(nèi)存然后丟棄掉前面1999條。你會看到接口OOM或者GC頻繁導(dǎo)致Full GC。4.2 方案一先分頁主實體再批量初始化關(guān)聯(lián)既然JOIN FETCH不能和分頁共存那就拆成兩步第一步用普通查詢只查訂單本身分頁交給數(shù)據(jù)庫第二步拿到當(dāng)前頁的訂單ID集合用IN查詢把關(guān)聯(lián)數(shù)據(jù)一次性裝回來。PageOrderEntity orderPage orderRepository.findByStatus(OrderStatus.PAID, pageable); ListLong orderIds orderPage.getContent().stream() .map(OrderEntity::getId) .toList(); ListOrderItemEntity items orderItemRepository.findByOrderIdIn(orderIds); MapLong, ListOrderItemEntity itemsGrouped items.stream() .collect(Collectors.groupingBy(item - item.getOrder().getId()));這里有一個細(xì)節(jié)findByOrderIdIn返回的關(guān)聯(lián)對象它的order屬性如果還是懶加載代理你可能在后續(xù)訪問item.getOrder()時發(fā)起額外的查詢。但因為你已經(jīng)有orderIds了某些場景下可以直接用id構(gòu)造一個引用Entity或者用loadById批量預(yù)置到當(dāng)前事務(wù)的持久化上下文里Hibernate就不會發(fā)SQL了。這也是“會話內(nèi)先占坑”的思路。我實際項目中更常用的方式是在主實體分頁查詢時就用EntityGraph抓單層關(guān)聯(lián)再單獨做一層明細(xì)匯總。這兩種操作各自可控不會在內(nèi)存里做大集合的笛卡爾積。4.3 方案二冗余字段或統(tǒng)計字段電商訂單列表這個場景真正要顯示的往往不是完整的訂單項列表而是“有多少種商品、共幾件、總金額”。強行把items實體加載出來只是為了算個數(shù)非常不劃算。我見過不少架構(gòu)為了省事就這么做了結(jié)果列表接口成了DB的噩夢。合理設(shè)計是在訂單主表增加冗余字段比如item_total_count、item_sku_summary事務(wù)中寫訂單項時同步維護冗余字段。查詢列表時直接查主表不需要碰關(guān)聯(lián)集合。這個方案的代價是寫入邏輯必須保證冗余數(shù)據(jù)一致但在絕大多數(shù)業(yè)務(wù)里訂單項一旦寫入數(shù)量和金額不會頻繁變化一致性風(fēng)險完全可控。這也算是用空間換時間的經(jīng)典思路。如果連冗余字段都不想設(shè)計還有一個退路單獨寫統(tǒng)計查詢不做實體關(guān)聯(lián)。Query(select o.id, count(i.id) from OrderEntity o left join o.items i where o.status :status group by o.id) ListObject[] findItemCountGroupByStatus(Param(status) String status);這種查詢返回的是標(biāo)量數(shù)據(jù)不會觸發(fā)任何懶加載也不會產(chǎn)生實體快照性能很好。缺點是拿到的數(shù)據(jù)要自己組裝到DTO里但相比N1帶來的痛苦這點麻煩不值一提。4.4 超大結(jié)果集游標(biāo)是最后手段有些報表類接口一次要導(dǎo)出幾萬條訂單并附帶每條訂單的明細(xì)。這類場景就算用IN批量查詢幾萬條實體的加載和組裝也會讓內(nèi)存吃緊而且Hibernate的一級緩存會持續(xù)膨脹。這時可以考慮用Stream配合readOnly和fetchSize逐批讀取或者直接用原生的FORWARD_ONLYJDBC游標(biāo)。Hibernate里可以這樣QueryHints(QueryHint(name org.hibernate.fetchSize, value 500)) Query(select o from OrderEntity o join fetch o.items where o.status :status) StreamOrderEntity streamOrdersWithItems(Param(status) String status);注意流式查詢要求事務(wù)保持打開狀態(tài)連接不能被提前釋放。另外如果一對多join fetch加stream結(jié)果集會因為join而放大所以要確保沒有分頁。這類查詢適合后臺任務(wù)不適合同步接口。我建議不到萬不得已不要上這一層先把前幾種方案做扎實。5. 實戰(zhàn)案例一個訂單列表接口的完整優(yōu)化過程5.1 現(xiàn)場問題模擬項目X上線第二周訂單列表接口開始出現(xiàn)偶發(fā)超時。監(jiān)控數(shù)據(jù)顯示該接口打到數(shù)據(jù)庫的SQL總量占整個服務(wù)SQL總數(shù)的65%其中大量SQL形如select * from t_order_item where order_id 123 select * from t_order_item where order_id 456 select * from t_order_item where order_id 789一個典型的N1。負(fù)責(zé)這個接口的是一個剛轉(zhuǎn)JPA不久的同學(xué)他在Repository里寫了這樣一個方法Query(select o from OrderEntity o where o.status :status) PageOrderEntity findByStatusWithPage(Param(status) String status, Pageable pageable);然后在Service層遍歷Page里的訂單打印訂單項數(shù)量。就這么簡單知識盲區(qū)直接變成生產(chǎn)事故。5.2 排查鏈路我當(dāng)時按下面幾步排查花了一整個下午現(xiàn)在把步驟復(fù)盤給你。第一步開SQL日志。一開始show_sql和format_sql打開發(fā)現(xiàn)日志里大量重復(fù)SQL。第二步確認(rèn)是否為N1統(tǒng)計單次請求SQL總數(shù)。用p6spy打印帶耗時的SQL日志確認(rèn)一次分頁請求每頁20條發(fā)出1條分頁SQL 20條order_item查詢。第三步定位觸發(fā)點。開啟use_sql_comments能看到類似/* load one-to-many OrderEntity.items */的注釋直接鎖定到Service層的for循環(huán)。第四步分析數(shù)據(jù)模型。發(fā)現(xiàn)OrderEntity.items用的是默認(rèn)LAZY沒有被任何fetch策略覆蓋。第五步確認(rèn)分頁場景。這一步最關(guān)鍵因為分頁場景不能直接用join fetch否則會觸發(fā)Hibernate內(nèi)存分頁我當(dāng)時測過把上面那個分頁查詢直接改成join fetch items后Hibernate直接把所有訂單讀入內(nèi)存再分頁一次請求加載了全表數(shù)據(jù)數(shù)據(jù)庫的臨時連接瞬間滿了。5.3 優(yōu)化落地方案最終我采用了兩步走策略。第一步主頁面分頁查詢保持普通查詢不加join fetch分頁交給數(shù)據(jù)庫LIMIT。第二步拿到當(dāng)前頁的訂單列表后收集所有orderId用一條IN查詢批量查出所有items然后在Service層按orderId分組組裝成Map。public ListOrderListVO getOrderList(int page, int size) { Pageable pageable PageRequest.of(page, size); PageOrderEntity orderPage orderRepository.findByStatus(OrderStatus.PAID, pageable); if (orderPage.isEmpty()) { return Collections.emptyList(); } ListLong orderIds orderPage.getContent().stream() .map(OrderEntity::getId) .collect(Collectors.toList()); ListOrderItemEntity itemList orderItemRepository.findByOrderIdIn(orderIds); MapLong, Long countMap itemList.stream() .collect(Collectors.groupingBy(i - i.getOrder().getId(), Collectors.counting())); return orderPage.getContent().stream() .map(order - new OrderListVO(order.getId(), order.getOrderNo(), countMap.getOrDefault(order.getId(), 0L))) .toList(); }優(yōu)化后一個完整請求的SQL數(shù)量從21條降為2條接口P99從800ms降到120ms。5.4 這類排查的通用套路事后我把這套排查總結(jié)成四條經(jīng)驗也分享給你。第一N1問題一定是在“主查詢 循環(huán)訪問”這個結(jié)構(gòu)下發(fā)生。見到for循環(huán)、stream、mapgetXxx、集合訪問這類代碼就要本能警惕。第二SQL日志里重復(fù)率超高的SQL是最大嫌疑。用p6spy的耗時輸出一眼就能看出累計耗時最多的SQL模板是什么。第三不要一上來就改代碼。先在紙上畫清楚這個接口要查幾層實體哪些層真正需要哪些層只是“順便”。N1很多時候是“用不到的數(shù)據(jù)被強行加載”造成的。第四優(yōu)化完一定要用日志驗證SQL條數(shù)。很多同事改完說“應(yīng)該好了”我問一句“日志里幾條SQL”答不上來。沒有SQL條數(shù)對比的優(yōu)化都是自我安慰。6. 容易忽略的N1變種與常見誤區(qū)6.1 單向關(guān)聯(lián)也能引發(fā)N1很多資料講N1都拿雙向關(guān)聯(lián)舉例容易讓人以為單向關(guān)聯(lián)就安全。實際完全不是。比如你有一個Service類只依賴OrderItemRepository通過ListOrderItemEntity findByOrderId(String orderId)查出訂單項每個item上都有ManyToOne private OrderEntity order當(dāng)你遍歷item并訪問item.getOrder().getOrderNo()時同樣會產(chǎn)生N1。這里的order是懶加載代理每訪問一個都會觸發(fā)查詢。解決辦法同樣簡單要么在這個查詢的EntityGraph里抓取order要么先把訂單信息按id批量查出來放一個Map里再替換引用。重點是單向關(guān)聯(lián)只是減少了映射的書寫量并不減少懶加載觸發(fā)次數(shù)。6.2 Open Session in View是N1的幫兇很多Spring Boot項目默認(rèn)開啟了spring.jpa.open-in-viewtrue。這個配置的意思是每個Web請求的整個生命周期都保持同一個Hibernate Session打開。好處是Controller層訪問懶加載屬性不會報LazyInitializationException代價是你會無意識地在任何地方觸發(fā)懶加載N1問題被這個開關(guān)徹底掩蓋。我在檢查項目時有個習(xí)慣新項目強烈建議把open-in-view設(shè)為false徹底斷了“偷懶”的念想。這樣在Service層不把關(guān)聯(lián)數(shù)據(jù)查全到Controller層就必然報錯逼著你在數(shù)據(jù)訪問層把關(guān)聯(lián)關(guān)系設(shè)計清楚。對于存量項目逐接口排查比一鍵改配置更穩(wěn)妥因為當(dāng)你把open-in-view關(guān)掉后可能出現(xiàn)一大堆LazyInitializationException需要逐個修復(fù)。6.3 誤區(qū)把LAZY改成EAGER就萬事大吉我前文已經(jīng)說過這是飲鴆止渴。原因有三。第一EAGER導(dǎo)致查詢持久化實體時無論你需不需要關(guān)聯(lián)數(shù)據(jù)都會JOIN查詢SQL可能變成多表大JOINcase量翻倍。第二多個EAGER集合疊加時結(jié)果集行數(shù)會指數(shù)膨脹產(chǎn)生笛卡爾積。第三列表頁、詳情頁、導(dǎo)出任務(wù)對關(guān)聯(lián)數(shù)據(jù)的需求完全不同EAGER是“全都要”沒有任何取舍余地。Hibernate官方文檔其實早就建議盡量避免EAGER默認(rèn)全部LAZY在查詢時精確控制。記住一句話映射層只定義關(guān)系不定義查詢計劃查詢計劃應(yīng)該由Repository上的fetch策略決定。6.4 分批查詢之后又出現(xiàn)的二級N1有時候你以為優(yōu)化完了結(jié)果在“批量初始化”這一步里又踩了新坑。舉個例子我用findByOrderIdIn批量查出訂單項后為了給前端返回商品名稱遍歷item訪問item.getProduct().getName()。如果product的關(guān)聯(lián)是LAZY這里馬上又觸發(fā)N1只是目標(biāo)從t_order_item變成了t_product。這類問題很好復(fù)現(xiàn)優(yōu)化方案本身引入了新的實體訪問路徑但沒有把新路徑一次性加載全。解決方式還是在批量查詢上做文章把product也加進EntityGraph或者用另一個批量查詢把product全部查出來做Map。我總結(jié)了一個實用經(jīng)驗法則每次對一個實體的List做關(guān)聯(lián)屬性訪問前都問一下“這個關(guān)聯(lián)屬性背后的實體我是不是已經(jīng)把它在當(dāng)前上下文里弄齊了”齊了就是安全的沒齊就是N1溫床。6.5 二級緩存誤用帶來的混亂最后提一個常見翻車現(xiàn)場為了優(yōu)化N1有人把訂單、訂單項都配上了Cacheable。結(jié)果訂單更新后關(guān)聯(lián)的訂單項緩存沒失效用戶看到的是“改了訂單狀態(tài)但明細(xì)還是舊的”。因為Hibernate二級緩存默認(rèn)是基于實體主鍵和集合的更新主表時不一定級聯(lián)清理關(guān)聯(lián)集合的緩存。這種數(shù)據(jù)一致性問題比性能問題嚴(yán)重得多查起來又是一整天的活。二級緩存存在的意義是緩存“不太會變化”的數(shù)據(jù)。訂單、庫存、余額這類數(shù)據(jù)我從來不放二級緩存。寧可多查幾次數(shù)據(jù)庫也不要讓用戶看到臟數(shù)據(jù)。7. 從框架層面根治組織層面的防御手段7.1 Repository層設(shè)計規(guī)范N1問題不是靠某一招就能永遠(yuǎn)避免的它更多是編碼習(xí)慣問題。我所在團隊沉淀了一套持久層開發(fā)規(guī)范這里分享給你參考。第一Repository方法上禁止直接返回實體集合統(tǒng)一返回DTO/VO或帶EntityGraph的投影。這樣做能強制讓開發(fā)者思考“我需要哪些字段”而不是“把整個實體給我”。很多字段根本用不上加載進來純屬浪費。第二所有關(guān)聯(lián)的fetch策略一律顯式LAZY禁止出現(xiàn)默認(rèn)EAGER。這個可以通過Code Review守住也建議在實體基類上寫統(tǒng)一注釋。第三如果Service層要在同一個事務(wù)里訪問多個關(guān)聯(lián)屬性盡量在查詢階段就用EntityGraph抓全。實在做不到就用批量查詢Map組裝。7.2 SQL審計自動化代碼審查防不住每一個漏網(wǎng)之魚更可靠的是自動化監(jiān)控。我有兩個比較推薦的做法。第一個在測試環(huán)境或預(yù)發(fā)環(huán)境打開SQL日志統(tǒng)計對單接口做SQL數(shù)量斷言。比如用JUnit的DataJpaTest集成測試在測試結(jié)束后統(tǒng)計Hibernate的SessionFactory.getStatistics().getEntityLoadCount()或者getCollectionLoadCount()超過閾值就報錯。這能保證新增代碼不會引入無休止的N1。第二個對關(guān)鍵接口做流量錄制或網(wǎng)關(guān)層的SQL監(jiān)控。把接口每分鐘的SQL總次數(shù)畫成曲線如果某個發(fā)布版本后曲線突然升高大概率引入了N1或大循環(huán)加載。我在項目中用過數(shù)據(jù)庫中間件的SQL審計能力效果很好能精確定位到機器和接口URL。7.3 團隊培訓(xùn)的價值看似是技術(shù)問題實際上是知識盲區(qū)。Hibernate的初學(xué)門檻低于MyBatis很多新手上手特別快但對懶加載機制缺乏敏感度。我建議團隊里定期做一次“SQL日志閱讀”的workshop拿一段真實日志讓大家猜這個接口里循環(huán)了多少次、查了幾次庫。這個訓(xùn)練做三輪之后團隊至少會養(yǎng)成“寫完查詢先看日志”的習(xí)慣。有一句話我在Code Review時經(jīng)常說你以為你寫的是一行Java代碼其實你寫的是三條SQL。把這句話內(nèi)化成團隊的默認(rèn)認(rèn)知N1的復(fù)發(fā)率會大幅下降。7.4 兜底方案全局懶加載監(jiān)控開關(guān)如果團隊規(guī)模較大、水平參差可以在框架層面加一個兜底保險。比如實現(xiàn)一個Spring AOP切面監(jiān)控Service方法執(zhí)行期間發(fā)起的SQL數(shù)量當(dāng)單方法SQL數(shù)超過閾值時打印告警日志并拋異常。這樣即使有人不小心寫了循環(huán)查庫的代碼也不會無聲無息上線。但這類兜底方案不能作為常規(guī)手段依賴。因為“SQL多”不等于“有問題”有些合法查詢本身就涉及很多子查詢。建議把閾值設(shè)得寬一些只做告警不做阻斷避免誤殺正常業(yè)務(wù)。8. 我踩過這些坑之后的一些最終體會N1問題看起來是個性能話題本質(zhì)上是個“控制意識”的問題。Hibernate給了你很大的自由度同時也把查詢計劃的責(zé)任移交給了你。你用MyBatis寫關(guān)聯(lián)查詢時每一條SQL都是你自己寫出來的代價直觀用JPA/Hibernate時SQL是隱式生成的代價藏在方法調(diào)用后面。這意味著你的心智模型必須跟上框架的抽象層。我個人的建議是剛接觸Hibernate時一定把所有懶加載信息摸透把SQL日志當(dāng)成日常開發(fā)的一部分不要等到線上告警才追悔莫及。我甚至?xí)诒镜亻_發(fā)時把show_sql常開配合一個不錯的日志格式化工具每寫一個查詢就停下來看一眼SQL條數(shù)。這個習(xí)慣幫我在很多項目里提前擋住了潛在問題。最后分享一個小技巧如果你的接口確實需要完整的實體圖數(shù)據(jù)又不方便使用EntityGraph可以考慮用DTO投影加對象組裝。不要總覺得DTO麻煩在性能問題面前一點類型轉(zhuǎn)換的開銷遠(yuǎn)比幾十條網(wǎng)絡(luò)往返便宜。把“少發(fā)SQL”作為指標(biāo)代碼多寫幾行完全值得。希望這篇經(jīng)驗對你正在處理的N1問題有實際幫助。如果在優(yōu)化過程中也遇到了奇怪的SQL膨脹問題歡迎多交流Hibernate這潭水多踩踩總會摸清底。
返回列表
PREV
查看更多資訊
NEXT
返回資訊列表
www.久久最新地址| 麻豆天美国美国产| 久久亚洲AV无码专区国产精品| 日本女厕偷拍| 五码视频在线观看| 久久久久久69国产一区二区| 亚洲色图欧美另类在线| αⅴ天堂| 伊人网高清| 精品四五区| 在线a v| 久久日韩肥臀| 国产精品九9| 百度百度日本操逼| 欧美综合狠| 欧美精品自慰系列寂寞少妇| 色九久| 人妻一区视频| 秋霞免费无码视频日韩A片 | 在线亚洲丝袜视频网站| 免费的黄片wwwwww| 精品久久无码午夜福利| 啊啊啊啊啊在线| 日韩97视频!在线| 九九九九九九九精品视频| 97视频网站| 97色色色综合网站| 三级片网站在线播放| 天天综合色图| 91综合天天| 成人熟女视频一区二区三区| 九九热AV| 操逼网免费无码视频| 91肉丝| 欧美一二级| 久久久久久久久久久久久久久性生活视频 | 91女日逼| 成人小电影网站tex| 欧美激情 亚洲色图| 9.1小视频| 热久久九九热| 五月丁香啪啪啪| 亚洲精品三区在线观看| 丰满少妇一区二区三区四区观看| 370p日韩欧美亚洲精品| 视频一区二区三区精品| 吖在线不卡一区二区国产剧情| 在线观看不卡一区二区三区| 午夜精品久久久久久久99蜜桃一| 综合色99| 插日本熟女视频| 国产午夜精品在线观看| 情趣丝袜无码操逼视频| 美女被艹尤物视频| 伊人国产av| 美美91成人国产精品欧美精品久久久久久久| 香蕉一区二区三区在线视频| 亚洲drav色图| 精品一区二区久久| 欧美日韩1234| 99在线免费公开视频| 国产亚洲日韩在线三区黑人| av天堂影视中文在字幕在线中文| 日日日骚女人精品| 9999久久久久| 日韩日本欧美在线观看| 密臀成人视频久久久| 久插综合| 亚洲欧美激情小说| 精品人妻一区二区免费看| 久久九精品| 69综合网| 日本高清熟女久久一区| 91天天综合日韩欧美| 0755午夜福利视频| 亚洲小电影免费涩涩成人在线高清| 日本操逼视频导航| 粉嫩av在线一区二区| 嫩草一区二区在线观看| 亚洲中文字幕噜噜噜久久久| 2020中文字幕| 午夜精品久久久久久久99热影院| 欧美情色男人的天堂| 超碰欧美97资源| 欧美综合天天| 97人人超| 秋霞男人网| 午夜视频久久久| 久久这里只有精品9| 亚洲国产精品无石码久久| 日韩欧美性吧婷婷乱伦大香蕉| 亚洲性网| 亚洲国产激情国产av| 久久久久久久久久久久久久久久9| 在线播放免费av福利片| av爱爱爱| 久久久极品| 精品一区二区三区国产 | 久久久中文版| 内射卯月麻衣| 人妻精品免费一二三区| 亚洲成人在线高清| 亚码激情| 久久久96| 91天堂网| 日本免费一区二| 舔人妻中文免费视频| 欧美精品二区视频在线| 激情图片亚洲色图| 欧美综合自拍| 久久久不能久久久久| 欧美天天影院| 成人羞羞视频国产| 国产一区二区视频在线播放| 亚洲视频精选| 超碰在线成人| 黄页av| 五月丁香激情综合网| 欧美熟妇亚洲版| 无码人妻精品一区二区中文| 伊人久久大香线蕉亚洲五月天,青草青草欧美日本一区二区,欧美日产欧美日产国产 | 91综合在线| 亚洲综合影片| 粉嫩av平台| 亚洲中文字幕乱码无码一区二区| 成人一二| 牛牛久久国产精品视频一二三| 中文字幕精品探花视频| 亚洲另类色图片| 国产精品爆乳懂色蜜乳| 亚洲棕合电彰| 美女自卫慰黄网站免费| 亚洲图片另类| 91欧美美女日韩国产婷婷| 日本操逼视频在线| 亚洲图片 激情小说| se01国产在线视频| 精品二区三四区五电影 | 久久久中文| 欧美亚洲高清不卡| 欧美亚洲另类在线蜜桃| 四虎免费视频| 天天色,天天干,天天干| 91天天c| 老司机深夜18禁污污网站| 精品一区二区麻豆| 日本午夜久久电影| 超碰97精品| 欧美视频第二页| 久久久久国产精品喷潮免费观看臀| 久久免费中文字幕在线观看| 激情抓乳插进去啪啪啪日韩| 国产视频大全| 丁香六月激情综合| 综合色区偷拍| 人妻社区男人天堂| 中文字幕在线高清男人的天堂| 亚洲色图欧美| 亚州欧美总和| 国产欧美伊人| julia高潮后不停追击中出| 青娱乐999| 日本中文字幕一区| 夜夜操二区| 成人免费毛片| 日本Suv精品一区二区| 国产情侣自拍在线播放| 校园春色亚洲| 玖玖大干人妻| 精品九九淫乱男| 日韩pv中文| 亚洲成?V人片在线观看福利| 性色AV网站| chaopen97久久| 蜜桃中文字日产乱幕4区| 国产精品久久99日日| 欧美日韩久久精品爱爱| 欧美色97| 欧美日韩制服| 久久久999国产精品| 五月天伊人| 大香蕉人妻| 最新的亚洲无吗| 啊啊啊好多水| 欧美日韩欧美| 久久久四区| 男人成人黄色视频在线观看免费下载| 97综合在线观看| 97人人草| 欧美黄色大香蕉一区二区| 国产偷人伦激情在线观看| 亚洲中文字幕久久无码精品| 偷拍新久久| 东京热精品97综合网| 国产老熟女| 一区二区你上我| 日韩去日本高清在| 欧美刺激色黄片免费看| 强奸乱伦日韩AV| 久久精品福利影院| 人妻中文字幕精品无码| 91人妻人人澡人人爽人人精品| 国产精选视频| 变态综合色| 人人操人人精品影片| 大香蕉琪琪日本女优不卡| 97一区二区蜜臀| 人妻久热在线| 欧美第38页| 日韩天堂av电影在线观看| 97操97色| 四季AV一区二区凹凸精品小说| 一本色道久久综合狠狠操| 久久九操在线观看| 亚洲精品丝袜| 婷婷激情一区二区三区俺也去| 澳门成人网站久国产日韩| 久草久热| 色 亚洲 91| 97日视频| 91国产丝袜美女| 91激情国产| rivers-china.com| 亚洲色电影在线| 9999久久久| 中文字幕精品一区二| 久久久久亚洲av综合波多野制衣| www.91逼逼.com| 91一区二区| 精品性爱一二三区| 五月丁香综合网| 人妻天天爽夜夜爽爽| 日韩天堂av电影在线观看| 久久国产精品,久久国产| 亚洲日韩青青草色月| 欧美在线 亚洲| 日韩欧美中文字亚洲慕| 综合 亚洲 欧美| 18禁止看精品中文字幕| 99热只有这里有精品| 狠狠色婷婷7777久| 国产25页| 亚洲欧美日韩激情不卡| 中文人妻av高清一区| 免费强奸av| 日韩av女优在线免费一区| 狠狠色一区二区中文字幕| 91情色在线| 美女熟妇色| 美中日韩无码| 加勒比无码一区二区三区| 欧美国产有色电影| 蜜臀一区二区三区亚洲最新章节在线观看 - 高清蜜臀一区二区三区亚洲全集播放 | 在线无码视频| 91N欧美| 美女久久久久久久久久久| 亚洲一区二区中文字幕| 亚洲人成网站7777| 欧洲熟妇xxXx欧美老妇裸体| 97自拍一区| 国产精品一区二区三区在线| 国产欧美日韩一区二区三区| 大香樵伊人网| 日韩91网站| 夜夜天天噜狠狠爱2021| 欧美情色亚洲| 韩国一级婬片A片无码天美 | 和协影院中文字幕三区| 日韩在线电影| A片三级无码| 久婷婷一区| 99热这里只有精品18| 极品尤物自安慰| 中文字幕日产av人| 91丨国产丨白浆秘 洗澡动漫| 欧美性爱另类综合| 91国产丝袜美女| 久久精品中文字幕无码l| 91精品成人www| 欧美性爱一级操| 亚洲色啪| 亚春色色| 精品亚洲国产成人av网站| 亚洲精品人妻吞精av| 九九九九久久久| 丰满少妇精品一区二区| 亚洲一区二区三区欧美日韩| 校园春色欧美| 黄页视频网站野外| 久久人妻少妇| 91|九色|国产熟女| 蜜区区视频79| 亚洲AO在线| 成人精品无码| 蜜臀AV成人精品蜜臀AV久久| 中文字幕人成乱码熟女香港| 亚洲av无码成人精品国产| www. 男人天堂成人在线| 啪啪免费| 精品大全99999| 国产成人精品日本亚洲语言| 91精品丝袜久久久久久| 翘臀vidoes| 亚洲日韩国产精品| 六月丁操逼| 国产乱伦亚洲| 另类专区加勒比| 日韩精品一区二区三区色欲| 色香av| 97久精品| 操逼视频亚洲| 婷婷激情四射| 少妇人妻精品| 久久久精品视频免费观看| 四虎884| 操久久久久久| 在线欧美69V免费观看视频| 天天操人人操骚逼网站| 亚欧高清v| 韩国一级做a久久久久| 狠狠图片青青草| 岛国黄色短视频| 9久9久| 97超碰国产亚洲精品资源| 95人妻爽爽人人做人人澡| 九九九九九九九九九国产精品| 欧美专区第一页| 秋霞成人一级在线观看| 91 天天综合| 闷骚老熟女15P| 天天色综亚洲91污| 亚洲高清无毛一区二区| 综合网少妇| 欧美成人亚洲精品| 亚洲aV性爱| 永久电影三级在线观看| 色777999综合| 亚洲欧洲第二视频在线观看色图| 午夜精品久久久久久久男人的天堂 | 乱老熟女一区二区三区| 久久婷婷伊人| www.黄色在线| 人人操人人插 - 百度 - 百度| 97超碰免费生活| 一区黄二区黄| 九月激情婷婷| 丝袜色综合| 人人手机欧洲亚洲国产人妻| 欧美黑人精品一区二区| 精品视频在线观看精品| 亚洲97| 91制服丝袜| 亚洲污污网站| 欧美 综合| 色噜噜狠狠色综无码久久合欧美| 福利伊人玖玖国产| 欧美1区二区三区公司| 日韩在线电影| 欧美日韩久久精品爱爱| 97精品一区| 五毛骚逼极品美女怕怕| 欧美情色男人的天堂| 91美女中出| 久久高清欧美国产| 93人人操人人| 最新日日夜夜天天干干| 久久精品一区| 97亚洲综合电影| 超碰99在线| 亚洲中文日韩精品| 亚洲国产午夜真人一级片中文字幕精品黄网站 | 欧美美女视频| 日韩精品午夜操呦呦不卡影院| 国产成人精品午夜福利| AV中文字幕剧情1区2区3| 久久一二三级一一一| 2020中文字幕| 欧美日韩夜夜| 啊啊啊啊视频免费| 久日91在线| 一区二区三区 丝袜高跟| 台欧久久精品视频| 99国产精品免费| 久久伊人青青草| 久久亚洲天天做| 超碰97网址| 久久久久七视频| 午夜精品久久久久| 岛国999| 色综合V| 国产综合日韩伦理| 欧美日韩黄片精品在线| 99re在线精品78| 精品无码一区二区| 亚洲精品天天影视综合网 | 婷婷五月天色| 欧美操人| 亚洲天堂人妻一区二区| 91肏屄网| 97视频在线免费播放| 在线色资源| 俺也射| 男人的天堂99| 97超碰人人操人人操| 先锋影音av先锋一区| 日韩免费簧片| 岛国AB视频| 婷婷视频网| 性色一线| 一区二区三区成人| 日本一卡二区在线| 成人线上超碰| 天天做天天爱天天高潮| 亚欧无码线免费观看视频| 日本操逼视频在线| 欧美性爱视频免费一区一A| 日韩一级欧美一级在线观看| 久久不卡一区二区| 乱码人妻一区二区三区| 69精品| 岛国网址国产 | 99视频内射三四| 在线黄页看毛片| 1204av韩国| 日韩大香蕉AV影片| 操www| 人妻精品视频一区二区| 国产精品情侣啪啪| 男人的天堂三级| 人妻无一区二区三区| 亚洲欧美色图| 手机看片1024你懂的国产| 骚货人妻偷情自拍在线视频| 亚洲在线A| 日韩一区二区精彩视频| 精品亚洲国产成人AV制服丝袜| 91色碰| 性色高清..……| 岛国黄片网站| 欧洲色色| 中出20p| 岛国网址国产 | 九九九九九用不成了| 日本在线伊人啪啪| 日本www操操操| 日本天堂网| av凤凰久久久| 日韩pv中文| 精品久久久久成人码免| 亚洲激情综合另类男同| 91 丝袜在线| 国产强奸乱伦欧美| 日韩有码一区三区| 人妻另类| 国产免费久久久久| 日韩综合第八区国产精品| 日韩乱伦AⅤ| 狠狠操,使劲操| 天天操天天干美女网址导航| yaouchengrenav| yaouchengrenav| 亚洲男人天堂2019| 神马午夜久久| 婷婷五月丁香五月| 久久精品国产亚洲粉嫩| 无码聚合| 一区超碰一区| 乱伦熟女区| 综合大香蕉美。| 欧美日韩国产高清在线一二三区| 日韩无码AB| 美女视频尤物网在线看| 97久久久精品| 欧美少妇性爱网站| 免看60秒涩涩视频| 天天爽天天操| 国产成人在线观看网址| 人人污日韩一区二区| 人人操人人93| 粉嫩AV一区夜夜嗨| 亚洲日韩美女中文字幕乱| 九九九九九九九| 最近2019中文字幕国语免费版| 试看60秒| 日本色色色| 蜜臀久久久99久久久久| 九九久久一区二区三区| 久久草草亚洲蜜桃臀| 天天α片| 色噜噜人妻丝袜a∨先锋影 | 国产一级作爱毛片| 99热在线只有精品| 热久久精品| 久久久久免费看少妇A片特黄| 日韩美女高潮喷水视频| 99re这里只有精品3| 午夜精品久久一区二区| 亚洲精品蜜桃久久久久久久| 日本岛国黄色网址| 一区二区三区免费视频入口| 插入粉嫩少妇视频| 精品女人999| 无码操逼视频一下| 人妻天天爽夜夜爽2| 国产乱码久久| 日本操逼视频导航| 亚洲欧美999| 国产第11页| 99免费视频| 国产精品视频麻豆入口| 欧洲视频在线| 亚av顶级裸体一区二区三区四区五区 | 亚洲强奸乱伦影视网| 欧洲精品一级二级精品综合视频综合 | 少妇与黑人高潮在线| 91社区拍啪人妻| 久久啊啊啊| 欧美论理片| 东北黄色电影| 久久婷婷热| 亚川综合视频| 欧美亚洲激情| 午夜精品久久久久久久第一页按摩| 久草资源在线视频官方总站日韩丝袜美腿| 91精品丝袜在线观看| 精品无码一区二区三区| 久久色精品视频在线| 无码操逼天堂| 欧美天堂超碰97| Av色五月| 亚洲国产第一页综合视频| 先锋激情∨在线视频播放| 97精| 中国特猛少妇色xxx| 欧美成人国产精品| 欧美A√综合网| 九九热免费国产视频婷婷伊人| 精品人妻美妇91job| 国产日产精品久久快鸭的功能介绍| 97欧美| 极品色综合| 97在线/亚洲| 夜夜操一区二区| 欧美日韩人人早| 一区久久久二区| 亚洲最大91网| 天天综合在线4| 五月天开心网| 国产免费一区二区三区最新不卡| 一级免费啪啪片| 性爱av网站| 天天做日日爱夜夜爽| 久久久久久久亚洲Av无码| 岛国激情视频在线观看| 亚洲AV成人无码一二三久久| 我要去看2个日本美女.com曹逼| 国产400孕妇孕交群| 青青草国产欧美非洲黑人| 久久精品无码专区| 亚洲天堂男人天堂网| 欧美97色| 91在线欧美| 亚洲学生妹高清av| 国产强奸AV在线| 大香蕉免费3| 吖在线不卡一区二区国产剧情| 操人妻少妇中文| 69超碰综合| 天天干天天日天天射黄色| 99热综合在线| 精品免费国产二区三区| 欧美日韩青操| 91日韩网站| juliaann欧美丝袜办公室| 久久久久久久97| 亚洲成人黄色在线观看| 国产精品无码久久久久2028| 五月天婷精品激情| 狂操嫩妻视频一区二区三区| 亚洲AO在线| 蜜桃一区二区三区| 欧美少妇高潮视频| 午夜国产成人精品视频| 国模不卡| 国产欧美日韩在线观看麻豆传媒公司| 熟女中出视频| 青青草导航在线视频| 91网站18| 亚洲1区2区三区高清中文字幕| 九九无码视频| 色女99一级片在线观看| 精品无码产区一区二| 亚洲动态色图| jizzjizz欧美| 日韩无码嘿咻黑热久| 嗯嗯啊啊好大好爽| 日本一二区不卡| 久久社区一区二区三区| 婷婷六月色| 使劲用力艹少妇视频一区二区| 人妻少妇无码| 影视综合无码少妇| 超碰97精品在线| 欧美国产操逼| 日本国产亚洲一区在线观看| 乱伦熟女区| 精品国产72| 狠狠爱夜夜| 福利视频一区二区微拍| 久久不卡一区二区| 亚洲男人的天堂va亚洲男人社| 看大黄色大片原件| 亚洲伊人久久精品狠狠在线| 日本道人妻久久久在线不卡色视频| 牛牛操视频逼| 97九色人妻| 亚洲图片视频小说| 日本无码1| 九九英色视频| 黑人猛交| 日本免费中文一区二区三区四区| 99福利社| 中文字幕一区二区韩| 嗯嗯啊啊用力视频免费| 无码78| 午夜人妻精品综合在线| www.成人无码| 在线无码操| 330dv亚洲成年视频网| 人人爱人人乐人人操| 六月丁香婷| 国产97/欧美| 国产亚洲精品第一最新| 天天综合网~91综合网| 久久欲| 69综合网| 超碰国产精品无码| 国产传媒1234区| 日本羞羞的视频在线播放| 久久精品一区二区三区不卡| 清纯唯美第一页| 第一高清av中文字幕| 东京热AV男人的天堂| 国产精品自拍欧美在线| 人人妻天天做天天爽| 黄色免费网页无码| 老女人91| 免费毛片在线播放| 亚洲图片欧美偷拍| 亚洲中文字幕噜噜噜久久久| 亚洲情欲| 亚洲一二三四区机械| 涩综合导航| 亚洲色香| 熟女六十路| 色yeye成人免费视频| 亚州男人的天堂| 在线中文字幕极品av| 欧美女同在线| 激情六月天| 最新无码国产| 东京热不卡视频| 久久AV无码网址| 人妻熟女一区二区三区视频| 91蜜臀熟女| 黄色香蕉视频网站一区| 亚洲欧美日韩电影网站一区| 天天精品| 欧美影院一区二区三区| 蜜桃视频一区二区三区| 国产做?爰片久久毛片?片美国| 日本一二三免费久久| 中文字幕二区日韩天堂| 777琪琪午夜免费A片| 欧美中字不卡| 日本有码影片下载| 亚欧视频在线| 伊人91| 天天天天天天天天综合| 国产精品久久久三级无码| 青青草国产欧美非洲黑人| 国产高清不卡视频| 90后性网国产欧美| 欧美精品成人一区二区在线观看| 99只有精品| 亚洲 日韩 欧美 国产综合体| 91视频伊人| 97网站在线观看| 欧美性战999| 在线国产探花| 久久精品国产99精品亚洲蜜... | 天天做日日做天天欢。| 九久精品| 欧洲精品欧洲精品| 中文字幕 av v| 最近2019中文字幕国语免费版| 夜夜躁狠狠躁日日躁av| 日本精品久久久久久久| 日本美女性生活久久久久久久| 国产h片在线观看视频| 亚洲影视综合| 午夜九九| 牛牛久久国产精品视频一二三| 很很干很很操| 操逼视频免费日韩无码| 91丨国产丨白浆秘 洗澡动漫| 91久久精品美女高潮喷水| 久插综合| 国产原创精品| 九九亚洲视频| 能直接看AV的网站| 免费观看国产不卡av| a'v在线资源| 中文字幕一区二区无码成人| 校园春色欧美| 免费网色网站| 亚洲熟女综合| 久久国产精品91| 五月丁香激情综合网| 亚洲精品一区二区三区在线播放| 日韩操逼性鲍| A级在线视频| 亚州综合色| 99re视频这里只有精品| 极品少妇久久久久| 日韩无码服务区| 精品综合久久久久久97| 97视频www| 青青草色情网站视频| 宅男91视频在线播放| 欧美一级三级| 亚洲成人碰碰| 九色 人妻 大香蕉| 亚洲色图激情小说| 亚洲有码 视频一区| 久久婷婷五月天| 亚洲欧美中文日韩视频中国语 | 免看60秒涩涩视频| 丁香7月婷婷| 国产精品久久久久999| 91欧美综合在线| 成人欧美日超碰| 超碰久久网| 色哟哟AV| 亚洲天堂另类| 啊v在线观看视频| 欧美色图 色综合图| 色综91| 欧美操人视频| 蜜乳AV.COM| 操逼啊啊啊91| 2020国产精品| 欧美极品少妇交| 欧美中文字幕一区 | 岛国激情视频在线观看| 少妇无码av专区线| 亚洲一本大道中文字幕无码在线| 丰满人妻大屁一区二区| 五月天婷婷影院| 亚洲中文字幕妇伦久久| 美女大乳久久久久久久女人18| 亚洲免费97免费| 丰满人妻一区二区中文| 日韩无码第3页| 99热| 伊人五月天激情| 亚洲图片欧美在线视频| 啊啊啊啊啊啊啊国| 国产精品一区二区后入| 黄色成年| 欧美爆乳精品一区二区| 人成午夜免费大片| 久草视频制服诱惑| 在线五区| 3PAV乱伦视频| 色综合久久88色综合久久天天| 天天干,夜夜爽| 乱老熟女一区二区三区| 亚洲中文丝袜美腿诱惑字幕| 香蕉欧美| 欧美一级黄片免费播放| 中文字幕一区 二 区 三 四 五 区日 日 骚 | 亚洲精品99999| 免费又黄又裸乳的视频| 九九热在线精品视频| 熟女欧美日韩综合婷婷| 黄色成年| 欧美日韩激情无码专区| 激激五月| 91蜜桃传媒精品久久久一区二区| 亚洲欧洲自拍图片专区满春格| 久久一区二区三区入口| 亚洲综合影视| 美女好片色日本| A级毛片在线看免费| 五月婷色| 超碰97欧美日韩| 久久夜夜| 亚洲乱熟女一区二区三区大香蕉| 青青草精品| 久久午夜色播影院免费高清| 蜜桃无码AV一区二区| 91成人在线| 国产传媒av天美传媒在线| 日本国产欧美高清在线| 天天综合欧美综合| 亚洲综合20p| 午夜福利成人免费视频| av东京热男人的天堂| 国产无码精品成人| 日本高清电影欧美色图| 全国男人天堂网| 国产浮力影院第1页| 色香在线| 老司机深夜18禁污污网站| 久久久久久性爱片| 熟妇色99| 九九99久久| 黄色成年| 久久婷婷伊人| 偷拍亚洲视频一区二区三区四区| 欧美日韩国产黄色片| 97超碰逼| 精品亚洲成人免费在线| 亚洲天堂另类小说男人| 精品999999| 懂色AV一区二区三区| 欧美性爱一区二区三区| 午夜一区| 欧美精品一二三| 长长久久免费视频| 夜夜欢天天干| 欧美日韩国产在线| 97AV在线观看| 97精彩视频网站| 17c嫩草51久久91嫩草| 久久久艹艹艹| 亚洲第一页第二页激情| 亚洲AO在线| 亚洲成aⅴ人片不卡无码| 懂色AV中文| 在线观看无码三级少妇| 色综合久久夜色精品国产天堂| 强奸乱伦亚洲第一页| 小明看看网址| 国产又操| 天天综合-91入口| 夜夜高潮夜夜爽| 国产日韩欧美中文在线播放| 久久动漫精品视频这里只有精品| 色官网色综合| 免费无码国产精品v片在线观看| 久久免费精品96| 欧美性爱十八禁| 99re黄 | 久九色| 成人精品视频一区二区| 欧美91视频| 91超碰丝袜制服| 韩国久久97| 黄视频免费| 天天躁夜夜躁狠狠躁AV| 成人欧美一区二区三区黑人一| 91天天综合| 熟女精品一区二区在线观看| 婷婷综合激情| 强奸抽插av| 啊啊啊好疼| 精品精品精品| 国产高清精品一区二区三区毛片 | 黑人操一区二区| 精品人妻一区二区三区不卡断| 色呦呦、国产精品| 亚洲成a人在线观看久| 99re久久| 伊人玖玖网| 99久久九九| 日韩欧美大力操| 日韩97超碰| 亚洲综合小视频小说在线观看| 在线观看黄色电话| 亚洲在线网站| 怡红院网站在线视频| 青青青国产| 国产视频不卡在线观看| 张柏芝国产一区在线观看| 亚洲涩图欧美| 99热精品青草在线| 毛片电影一区二区三区| 亚洲欧美色图小说| 大逼色网站| 精国久久一区二区三区98| 激情五月婷婷| 国产亲戚伦亲在线| 爱妃国产亚洲视频中文字幕| 一卡二卡三卡| 国产精品视频在线播放 | 亚洲人妻日日日| 欧美论理片| 宗合情欲网| 先锋精品av色鲁| 国产亚洲禁久一区二区| 国产一级高跟丝袜| 丁香婷婷五月| 中日韓欧美高清| 色原狠狠天天天| 欧美91久久久久| 麻豆伊人网| 红桃视频高潮| 97资源超碰| 思思热在线观看| 久久美女福利是上海美女| 91午夜无码| 国产真乱mangent| 中文一区二区三区影院| 欧美 亚洲精品首页| 青青草在线视频美女| 亚洲偷91色| 亚州色国| 密臀AV在线| 91九九九逼| 精品无码一区二区| 日韩成人色图| 99re6国产精品99re在线| 日本欧美成人片AAAA| 国内三级自拍小视频在线观看| 久久av成人无码免费| www.伪伪| 久久午夜鲁丝片| 久热影视| 国产精品嫩草久久久久| 影视综合无码少妇| 人人看人人爰人人操| 日韩99精品视频综合区| 欧美日韩日产免费网站看| 色操逼网| 精品久久久久av影院| 天堂中文日本在线观看| 女同性恋久久| 亚洲国产无码精品首页久久久| 一区二区三区欧美激情| 超碰av在线| 另类图片五月天| 天天日老熟妇| 少妇诱惑视频| 大香蕉男人的天堂| 亚洲自拍欧美色综合| 日韩97视频| 天堂亚洲精品| 国产乱子伦一区二区三区在线观看| 五十路一区无码| 国产在线综合福利网站| 男女做爰猛烈动高潮A片免费应用 少妇厨房愉情理伦片bd在线观看 不卡中文字幕aⅴ在线 | 亚洲AV资源| 日韩中文字幕av在线播放| 欧美情色贴图| 日本五区不卡| 青娱乐91| 影音先锋乱伦资源| 99久久久er直播网址| 色婷视频| 久草视频制服诱惑| 亚洲综合情色| 欧美大香蕉专区网| 亚洲最大无码中文字幕网站| 天天综合网站| 国产天天看| 亚洲做性| 日韩免费中文字幕视频| 青娱乐国产精品| 大奶啊啊好爽 | 中文字幕亚洲热播人妻| 色婷婷久久| 蜜奶av| 四季av一区二区凹凸精品小说| 另类图片天天影视| 26uuu性物| 淮穴色AV| 久久精品成人一区二区三区蜜臀| 性91| 伊人五月天激情| 欧美天天插| 91男人天堂网| 人妻一区二区三区四区视频| 久久久久久久久成人av解说| 熟女丰满人妻一区| 四虎在线免费视频| 天天日天天干天天操| 亚洲第一黄色av网站| 久久免费老司机精品| 久久美国毛片| 国产在线能看的你懂的| 精品一区二区成人动漫| 久久久久久久久久久久黄色| 精品人妻少妇| 人人爱人人操人人性| 欧美日本久久精品一区| 欧美高清91| 国语国产操逼伊人AV网| 成年男人的天堂| 国产美女高潮视频| 欧美黑人精品在线播放| 日本天天人人狠狠在线日美女| 激情文学小说一区二区| 浓厚中出中文字幕在线| 校园春色之综合网| 日日骚av| 91精品人妻电影| 啊操爽品善一区二区三区| 激情色图| 丁香五月AV| 亚洲自拍小说| 国产综合永久精品日韩鬼片| 天堂国产AV| 四虎精品永久在线播放| 日韩精品99999| 天堂av最新电影网| 国产熟女乱论| 色欧洲97| 黄色在线网站| 欧美亚洲日韩16色| 国产一区二区在线播放量| 久热色情精品| 丰满熟女一区二区三区在线播放| 色色色网站| 亚洲丨在线| aaa一级黄片| 日本一卡二区在线| 黑人精品成人一区二区三区| 天天操美美| 亚洲精品xxx| 国产妇女精品视频青青草| 日本色色视频网站| 美女的肌被草喷水视频| 成年人黄色视频免费| 亚洲人精品久久久喷水| 五月亭亭六月丁香| 日本美女性生活久久久久久久 | 大香蕉综合| 99久久久久久亚洲精品不卡| 蜜奶av| 一区二区三区高清天码| 久草男人天堂| 日韩Va亚洲va欧美Ⅴa久久| 国产第二页| 欧美网站免费| 一区二区三区麻豆| 激情自拍 校园春色| 欧美91精品国产自产| 亚洲 中文 女同| 久久亚州精品成人Av无| 色婷婷淫色网| 亚洲精品人妻吞精av| 中国熟妇| 爱干爱射网啊啊啊| 欧美国产精品| 久草成人影片| 思思99热| 国产在线能看的你懂的| 少妇内射www在线观看视频| 欧美东京热精品A∨| 校园春色 亚洲| 国产视频三区四区| 久久久久久久伊人精品| 久欲AV| 久久精品人体AV| 亚洲欧洲无码bt精品合集| 国产欧美成人精品| 婷婷五月激情综合| 国产 日韩,欧美 自拍| 天天综合网1| 亚洲一区中文字幕一区| 在线观看中文字幕| 久热大香蕉网站| 久久,精品一二三| 国产黄色在线播放观看| 成人资源中文字幕在线观看| 欧美1727免费观看视频| 日韩成人在线性爱视频| 欧美色交| 中文字幕88av在线| 亚洲综合色婷婷| 韩国手机不卡无码三级视频| 欧美97av| 日韩ab网 | 久久久涩| 伊人久久大香线蕉亚洲五月天,青草青草欧美日本一区二区,欧美日产欧美日产国产 | 午夜AV污污污| 亚洲情色婷婷五月天| 日韩精品1区2区中文字幕| 丁香五月影院| 国产精品不卡av免费在线观看| 国产精品高朝久久久久久久| 欧美日韩国第一区| 秋霞视频一区二区| 精品久久久久久中文字幕三区| 青青青在线高清视频在线一二三四区| 中国黑人三级片网站上区| 国产精品国产拍高清AV| 国产中文字幕在线点播| 96麻豆精品一区二区三区| 国产一区二区在线播放,久久亚洲精品中文字幕第一区,亚洲精品在线中文字幕视频 | 国产操操日韩三级黄| 97爱爱爱| 国语精品av| 夜夜夜夜爽| 久久久久久久久久黄色网 | 九九综合九九综合| 色原狠狠天天天| 青青草玖玖爱| 97 视频在线| 五月天春色激情网| 五月丁香综合| 亚洲日本韩国在线| 404操逼福利视频| 欧美九一精品久久久熟妇| 一起草视频在线| 国产亚洲日韩欧| 另类老少妇| 色综合av男人天堂| 人人妻人人爽 97人人看碰人免费公开视频| 玖玖无码超碰| 熟女露脸激情自拍视频| 青女在线| 黄色不卡视频| 亚洲色图加勒比| 久久久影院| 欧美日韩m| 男人的天堂啪啪啪啪啪蜜桃不卡| 久久精视频美日韩在线视频| 色黄色美女大长腿午夜视频| 亚川综合视频| 欧美性爱18观看| 69XX一中文字幕人妻91| 人人操人人色网| 极品国产内射| 超碰在线人人射| 一级黄色性爱裸体视频| 亚洲无码精品AV久久久| 呦呦一区| 视频国产成人精品日本亚洲18| 91精品国产91综合久久蜜臀| 呦呦一区| 综合影院永久入口国产| a片偷拍视频| 久久久精品电影| 亚洲欧美日韩二区视频| 99无码| 色婷婷综合网站| 五月天丁香网| 性吧在线视频| 97网址www| 久热久一区二区三区| 丝袜综合色图| 狠狠2050在线观看| 91美女视频电影| 阿姨一区二区免费视频-高清正片西瓜视频下载app-T450AV | 久久久久久久强迫| 综合激情二| 色伊人91|