Nvidia GeForce RTX 3060 顯卡將於 2 月上市,台灣售價 10900 元起_如何寫文案

※別再煩惱如何寫文案,掌握八大原則!

什麼是銷售文案服務?A就是幫你撰寫適合的廣告文案。當您需要販售商品、宣傳活動、建立個人品牌,撰寫廣告文案都是必須的工作。

雖然上個月才剛推出了 GeForce RTX 3060Ti,Nvidia 今日(1/13)發表了最新 GeForce RTX 3060 顯卡,新增的功能與稍低的價格希望能讓許多還在用舊款 GTX 1060 Pascal 顯卡的電腦玩家們能以更輕的負擔轉換,這款新顯卡台灣定價為 10900 元起。

Nvidia GeForce RTX 3060 顯卡將於 2 月上市,售價 329 美元起

Nvidia GeForce RTX 3060 內含 12GB GDDR6 顯卡記憶體,與其他 RTX 3000 系列一樣支援 Nvidia RTX 應用套組與 DLSS,據官方表示其光追效能將提高 10 倍,這點對於希望對電腦進行全面或局部零組件升級的人頗具吸引力。Nvidia 的 DLSS 技術採用神經網路和 AI 超級運算來分析遊戲並以較低的解析度使圖像更銳利,DLSS 允許遊戲以較低解析度進行渲染,並透過 Nvidia 的影像重建技術放大並使它看起來就像原生 4K 或更好的顯示效果。

根據 Nvidia 的說法,RTX 3060 具備 13 Shader-TFLOPS、25 RT-TFLOPS 與 101 Tensor-TFLOPS 的效能,雖然這不一定是遊戲效能的代表範例,但能夠充份表現出 RTX 3060 與 GTX 1060、RTX 2060 對比時擁有極為明顯的幀速提升:

※教你寫出一流的銷售文案?

銷售文案是什麼?A文案是廣告用的文字。舉凡任何宣傳、行銷、販賣商品時所用到的文字都是文案。在網路時代,文案成為行銷中最重要的宣傳方式,好的文案可節省大量宣傳資源,達成行銷目的。

在發表直播中,Nvidia 還展示了多款支援 RTX 或 DLSS 技術的遊戲,並且已經有 2 款電競遊戲加入了 Reflex 低延遲技術。這款新的桌機顯卡預計會在今年 2 月上市,屆時還會有更多 OEM 廠版本可供挑選,想要用更實惠的價格升級顯卡或預算有限的新機組裝玩家,千萬不要錯過這款看起來非常超值的產品。

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。

ROG 遊戲筆電 Zephyrus、Strix、Flow 與 TUF 全陣容更新,輕薄、強效一次擁有_網頁設計公司

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

透過資料庫的網站架設建置,建立公司的形象或購物系統,並提供最人性化的使用介面,讓使用者能即時接收到相關的資訊

CES 2021 開展,Asus 旗下專司遊戲與電競類產品的 ROG 這回趁勢推出不少產品,其中包含各系列的多款筆電,不管是重兵器等級或是輕薄硬漢,陣容相當精采,要外型有外型,內在更是威力驚人,讓你隨身擁有高行動力,無論工作、娛樂,一機在手什麼都行。

ROG 遊戲筆電全陣容更新,輕薄、強效應有盡有

ROG Zephyrus G14 / G15 / Duo
新一代的西風之神來了!在這一代中,Asus 以 AMD 最新處理器與與 Nvidia 最新顯卡的搭配來對 Zephyrus G14 / G15 進行一系列的更新。除前述兩部分的提升外,兩款機型皆提供新的顯示器選項,G14 現在附帶 144Hz 顯示器,另外還加上 2K 120Hz 的版本,Asus 表示能夠涵蓋整個 DCI-P3 色域;G15 則配備了 165Hz QHD 顯示器,同樣擁有 100% DCI-P3 色域。

在上蓋的 AniMe Matrix LED 像素 LED 顯示部分還增加了新的動畫效果,用戶可以將虛擬寵物設定在上面與自己進行互動(雖然旁人看你逗弄筆電會覺得很詭異)。

Zephyrus G15 在設計上有一些另外的改良,師法 G14 受歡迎的 ErgoLift 轉軸,在翻蓋後可以將鍵盤稍微抬高以提升散熱效果,觸控板部分也比前一代大了 20%,還將麥克風從過去的 2 個增加到 3 個,另外在上蓋 AniMe Matrix LED 部分則換成了可依照光線角度不同閃閃發亮的幻彩反光設計。。

兩款機型皆配備有改良過的喇叭,內建 Dolby Atmos 軟體與雙向 AI 消噪功能,另外這兩款機型都擁有 90Wh 的電池容量。Zephyrus G14 / G15 售價並未公布, 預計會在 2021 年第一季上市。

在去年首度推出的雙螢幕旗艦款 Zephyrus Duo 今年也有更新,採用 AMD 處理器(最高支援 Ryzen 9 5900HS 及 Nvidia GeForce RTX 3080),螢幕也採用 4K / 120 Hz 的 IPS 面板,外觀改為黑色,沒有打開蓋子前真是要多低調有多低調。


ROG Strix 2o21

這回 Strix 共推出有兩個不同的系列,分別為 Strix G 與 Strix Scar,共四種不同型號。
Strix Scar 以黑色為主色,再搭配橡膠碳灰、黑色、銀色與桃紅的可客製轉軸蓋,A 件和上抗指紋的亮面 ROG 像素圖騰, C 件局部半透處理,在機身側面飾以相當醒目的 RGB 光條,上蓋更免不了象徵品牌的發光 Logo,機身底部採用多個大型腳墊,真正做到防滑穩定的效果。以隱藏式 Air Arm 取代傳統轉軸設計,在闔上筆電時不會直接暴露在外。

Strix G 則以霧面與 Pixel 像素圖騰搭配的鋁合金 A 件和 C 件,共有黑、、灰兩色,搭配黑、橘兩色轉軸蓋,上蓋的 ROG Logo 為鏡面設計,同樣在機身側面配置 RGB 光條。

除標準版本外,還有一款桃紅色特別版,在上蓋以各種 ROG 產品原色的特色圖紋,加上桃紅色轉軸蓋、ROG Logo,WASD 按鍵也是用特別的桃紅色點綴,當然還有桃紅色的大型腳墊,同捆隨附桃紅色大型滑鼠墊。

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

當全世界的人們隨著網路時代而改變向上時您還停留在『網站美醜不重要』的舊有思維嗎?機會是留給努力改變現況的人們,別再浪費一分一秒可以接觸商機的寶貴時間!

這兩款機型在鍵盤部分採用 Asus 自有光軸機械鍵盤,而玻璃觸控板部分也加大了 85%,可切換為獨立數字按鍵模式,另外在方向鍵部分也加大,讓大家按得更順手,連接埠主要配置於機身上側,外接各種裝置也不卡手。在這一代的螢幕佔比相較於去年更加提升,極窄邊框副與它 85% 的螢幕佔比,體型卻比去年的機型更小。硬體配備方面採用最新 AMD 處理器、Nvidia GrForce 獨立顯卡,特殊的靜音散熱設計以最低的噪音來維持系統的穩定,螢幕更新率最高可達 165Hz。配備 90Wr 電池,續航最高可達 12.7 小時,支援快速充電技術,從 0 充到 50% 電力僅需 30 分鐘,搭配隨附的 100W USB-C  PD 充電器更是事半功倍。

ROG Flow X13、XG Mobile
Flow X13 是 ROG 系列中首度出現的 2in1 翻轉筆電,鎂合金機身上蓋呼應品名以具流動感的深淺線條始知看起來別具質感,機身可依照個人需求 360 度翻轉,在上蓋還有金屬 ROG 銘牌畫龍點睛。厚度僅有 15.8mm,重量只有 1.3Kg,輕薄好帶卻又不犧牲高效能。上覆康寧大猩猩玻璃的 13.4 吋觸控螢幕搭配極窄邊框,以 16:10 的顯示比例,為你帶來更好的視覺效果。採用最新 AMD Ryzen 9 8 核心處理器,Nvidia GeForce 獨立顯卡,記憶體最高可達 32GB LPDDR4X 4266MHz (雙通道),儲存容量最高可至 1TB M2 NVMe PCIe 3.0 SSD。支援快速充電技術,可在 30 分鐘內將設備電力從 0 充至 50%

XG Mobile 是一款採用 PCIe Gen 3 的 EGPU,承襲 Flow 的流動線條設計,從正面 ROG 鏤空中可見紅色燈光若隱若現,在機背部分配備有支架,以確保良好的空氣流動散熱效果,內建 280W 充電變壓,重量 1kg,厚度僅 29mm,在機身上配置有相當豐富的連接埠,可供你隨需求擴充外部裝置。

ASUS TUF Dash
TUF 系列以強韌、輕薄便攜與強效為主軸,這回的新機承襲這些因素,以更讓人驚豔的樣貌登場。ASUS TUF Dash 通過 MIL-STD 軍規認證,設計簡潔,視覺上相當時髦,深灰、月白兩種機身配色經典耐看,19.9mm 的厚度,重量只有 2.0Kg,有別於一般強效遊戲筆電帶來的厚重感。IPS 螢幕擁有 240Hz 的高更新率與迅捷反應速度,視覺效果超順暢。擁有藍色背光靜音鍵盤,可承受 2 千萬次敲擊,在 WASD 按鍵部分還重點突出,讓你玩遊戲時手指定位一目了然。

這款機型為全球首款搭載第 11 代 Intel H35 系列處理器的筆電,也將會搭載最新的 Nvidia 獨立顯卡。在記憶體與 SSD 部分因應使用者需求採易於升級的設計,擁有更多未來擴展空間。內部採用 5 根散熱管加上低噪音自我清潔冷卻風扇系統,散熱速度快,維持高負載運行時的系統穩定性。支援高速傳輸的最新 Thunderbolt 4 與 WiFi 6 連接,以及 USB-C 100W PD 充電,搭載最新快充技術,30 分鐘即可充至 50% 電力。

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

※想知道最厲害的網頁設計公司嚨底家"!

RWD(響應式網頁設計)是透過瀏覽器的解析度來判斷要給使用者看到的樣貌

Nvidia 終於將 RTX 3000 系列導入筆電中,本月起新機陸續現身_網頁設計

※推薦評價好的iphone維修中心

擁有專業的維修技術團隊,同時聘請資深iphone手機維修專家,現場說明手機問題,快速修理,沒修好不收錢

距離桌上型電腦用的 GeForce RTX 3000 系列顯卡推出不到半年時間,今日(1/13)Nvidia 宣布將為筆電導入 RTX 3060、RTX 3070 和 RTX 3080 等顯卡,憑藉其新的 Ampere 架構,與過去搭載 RTX 2000 系列的筆電相比擁有顯著的效能提升。

Nvidia 終於將 RTX 3000 系列導入筆電中,本月起新機陸續現身

在這次會中,Nvidia 表示,從 1/26 起將會有超過 70 款遊戲筆電配備 RTX 3070 和 RTX 3080 顯卡,而伴隨 RTX 3060 的推出,採用 RTX 3060 顯卡的筆電也會在 2/2 上市。搭載 RTX 3060 的筆電售價從 999 美元起;RTX 3070 筆電的售價則為 1,299 美元起,Nvidia表示在速度上會比以往的 RTX 2070 系統快上 1.5 倍,並且能提供 1440P、90fps 。RTX 3080 筆電的售價為 1,999 美元起,顯卡記憶體最高達 16GB GDDR6,超越 RTX 3080 PC 版,Nvidia 指出這些筆電將可提供 144oP、100fps 以上的幀速。

這些配備最新 RTX 3000 系列筆電採用的是第三代 Nvidia Max-Q 技術,而其變異版本則專為更輕薄的遊戲筆電而設計,主要在將大部分 GPU 熱能從更緊湊的機身中散出,而 Nvidia 與 OEM 廠商對此進行了更進一步的調整改良,以實現此一目標。Nvidia 表示,最新一代的 Max-Q 筆電將提供前代兩倍的效能,並且在這些筆電中皆運用了 Dynamic Boost 2.0 技術,利用 AI 以幀為單位平衡 CPU、GPU 與 GPU 記憶體之間的電力需求,達到即時平衡的效果。

最新 RTX 3000 系列筆電效能強大,足以運行對硬體要求嚴苛的《Microsoft Flight Simulator》,且 Nvidia 還在這些筆電中加入 WhisperMode 2.0,另外 AI 還能管理風扇速度和遊戲設定以保持所需的音位。

網頁設計最專業,超強功能平台可客製化

窩窩以「數位行銷」「品牌經營」「網站與應用程式」「印刷品設計」等四大主軸,為每一位客戶客製建立行銷脈絡及洞燭市場先機。

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

台北網頁設計公司這麼多該如何選擇?

網動是一群專業、熱情、向前行的工作團隊,我們擁有靈活的組織與溝通的能力,能傾聽客戶聲音,激發創意的火花,呈現完美的作品

Razer 展示 Project Hazel 概念,號稱全球最智慧口罩_租車

※超省錢租車方案

商務出差、學生出遊、旅遊渡假、臨時用車!GO 神州租賃有限公司!合法經營、合法連鎖、合法租賃小客車!

今天(1/13) Razer 在官網上發表了一款號稱全球最智慧的口罩「Project Hazel 」,雖然只是概念品,但對於疫情期間又不甘跟別人一樣的人們來說的確具有相當大的吸引力,對筆者這個悶騷個性的人來說更是有種躍躍欲試的感覺,你以為它只是口罩?不,它還會發光跟濾淨空氣呢!

Razer 展示 Project Hazel 概念,號稱全球最智慧口罩

外型上,Project Hazel 效法 N95 口罩,具備光滑、防水、耐磨的再生塑膠透明外殼搭配矽膠護照,與臉部緊密貼合,可重複使用,當你與他人對話時,還能讓對方看到你的嘴型與完整面部表情。在 Project Hazel 上還內建有麥克風與喇叭,可以確保對話時的聲音能夠讓對方清晰聽見;在口罩上還配有光感應器,當處於較暗環境中時,內部的燈光會自動點亮,以便讓別人看見你的臉孔,當然這個色光也能透過 Razer Chroma RGB 軟體來自定顏色。

在口罩外的兩側配置了兩個圓形區塊,它們主要的功能是用來使空氣流通,利用主動式圓形通風裝置,將呼出的二氧化碳排出,並且對吸入的空氣加以過濾,根據 Razer 說法,內部可替換的過濾裝置預計可過濾至少 95% 的微粒,且具備有高度細菌過濾效果,因為具防水性,自然也能防止液體、口水噴濺。在圓形區塊上還配有 RGB 色光,光是這點就足夠讓你與眾不同了。

隨著口罩本體,還附有一個內部配置紫外光燈的充電收納盒,當你將口罩放入其中時,可自動消滅附著在口罩上的細菌與病毒。這款概念產品目前還沒有上市相關資訊,對於必要的送審與核准當然也還沒有進行,但如果順利量產的話,似乎很值得入手啊!

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

日本、大陸,發現這些先進的國家已經早就讓電動車優先上路,而且先進國家空氣品質相當好,電動車節能減碳可以減少空污

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

有別於一般網頁架設公司,除了模組化的架站軟體,我們的營業主軸還包含:資料庫程式開發、網站建置、網頁設計、電子商務專案開發、系統整合、APP設計建置、專業網路行銷。

shell script的簡單使用_網頁設計

台北網頁設計公司這麼多該如何選擇?

網動是一群專業、熱情、向前行的工作團隊,我們擁有靈活的組織與溝通的能力,能傾聽客戶聲音,激發創意的火花,呈現完美的作品

shell script的簡單介紹

shell變量

1.命名規則

  • 命名只能使用英文字母,数字和下劃線,首個字符不能以数字開頭
  • 中間不能有空格,可以使用下劃線(_)
  • 不能使用標點符號。
  • 不能使用bash里的關鍵字

2.定義變量:name=value
3.使用變量:$name
4.只讀變量:readonly name
5.刪除變量:uset name
6.變量類型

  • 局部變量:當前shell有效
  • 環境變量:所有的應用程序有效

shell字符串

  1. 雙引號:可解析變量,可以出現轉義字符
  2. 單引號:不解析變量,原樣輸出
  3. 字符串拼接:
name="hello"
# 使用雙引號拼接
greeting="hello, "$name" !"
greeting_1="hello, $name} !"
echo $greeting  $greeting_1
# 使用單引號拼接
greeting_2='hello, '$name' !'
greeting_3='hello, ${name} !'
echo $greeting_2  $greeting_3

輸出結果為:

網頁設計最專業,超強功能平台可客製化

窩窩以「數位行銷」「品牌經營」「網站與應用程式」「印刷品設計」等四大主軸,為每一位客戶客製建立行銷脈絡及洞燭市場先機。

hello, hello ! hello, hello} !
hello, hello ! hello, ${name} !
  1. 獲取字符串長度
string="abcd"
echo ${#string} #輸出 4
  1. 提取子字符串
string="hello world"
echo ${string:1:4}#輸出 ello
# 字符串下標從0開始
  1. 查找子字符串
string="hello world"
echo `expr index $string e`#輸出2

7.反引號和$()一樣,裏面的語句當作命令執行

shell數組

  1. 數組定義:arrname=(value0 value1 value3)
  2. 數組讀取:${arrname[]},中括號內為數組下標,從0開始
  3. 獲取數組長度:length=${#arrname[@]},*號也可以
  4. 或許數組單個元素的長度:lengthn=${#arrname[n]},n為數組下標

shell註釋

  1. 單行註釋:#
  2. 多行註釋:<<EOF以EOF結束,EOF可以是任意字母

shell傳遞參數

  1. 向腳本傳參
  • $0:執行的文件名
  • $1:為第一個參數
  • $2:為第二個參數 以此類推
  1. 特殊參數
  • $#:傳遞到腳本的參數個數
  • $$:腳本運行的當前進程ID
  • $!:後台運行的最後一個進程ID
  • $?:回傳碼,显示最後命令的退出狀態
  • $@,$*:显示所有傳遞的參數

shell運算符

  1. 算數運算符

假設變量a=10,b=20

運算符 說明 舉例
+ 加法 expr $a + $b 結果為 30。
減法 expr $a - $b 結果為 -10。
* 乘法 expr $a \* $b 結果為 200。
/ 除法 expr $b / $a 結果為 2。
% 取余 expr $b % $a 結果為 0。
= 賦值 a=$b 將把變量 b 的值賦給 a。
== 相等 用於比較兩個数字,相同則返回 true。 [ $a == $b ] 返回 false。
!= 不相等 用於比較兩個数字,不相同則返回 true。 [ $a != $b ] 返回 true。
  1. 關係運算符
運算符 說明 舉例
-eq 檢測兩個數是否相等,相等返回 true。 [ $a -eq $b ] 返回 false。
-ne 檢測兩個數是否不相等,不相等返回 true。 [ $a -ne $b ] 返回 true。
-gt 檢測左邊的數是否大於右邊的,如果是,則返回 true。 [ $a -gt $b ] 返回 false。
-lt 檢測左邊的數是否小於右邊的,如果是,則返回 true。 [ $a -lt $b ] 返回 true。
-ge 檢測左邊的數是否大於等於右邊的,如果是,則返回 true。 [ $a -ge $b ] 返回 false。
-le 檢測左邊的數是否小於等於右邊的,如果是,則返回 true。 [ $a -le $b ] 返回 true。
  1. 邏輯運算符
運算符 說明 舉例
&& 邏輯的 AND [[ $a -lt 100 && $b -gt 100 ]] 返回 false
|| 邏輯的 OR [[ $a -lt 100 || $b -gt 100 ]] 返回 true
  1. 布爾運算符
運算符 說明 舉例
! 非運算,表達式為 true 則返回 false,否則返回 true。 [ ! false ] 返回 true。
-o 或運算,有一個表達式為 true 則返回 true。 [ $a -lt 20 -o $b -gt 100 ] 返回 true。
-a 與運算,兩個表達式都為 true 才返回 true。 [ $a -lt 20 -a $b -gt 100 ] 返回 false
  1. 字符串運算符
    假設變量a=hello,b=world
運算符 說明 舉例
= 檢測兩個字符串是否相等,相等返回 true。 [ $a = $b ] 返回 false。
!= 檢測兩個字符串是否相等,不相等返回 true。 [ $a != $b ] 返回 true。
-z 檢測字符串長度是否為0,為0返回 true。 [ -z $a ] 返回 false。
-n 檢測字符串長度是否不為 0,不為 0 返回 true。 [ -n “$a” ] 返回 true。
$ 檢測字符串是否為空,不為空返回 true。 [ $a ] 返回 true
  1. 文件測試運算符
操作符 說明 舉例
-b file 檢測文件是否是塊設備文件,如果是,則返回 true。 [ -b $file ] 返回 false。
-c file 檢測文件是否是字符設備文件,如果是,則返回 true。 [ -c $file ] 返回 false。
-d file 檢測文件是否是目錄,如果是,則返回 true。 [ -d $file ] 返回 false。
-f file 檢測文件是否是普通文件(既不是目錄,也不是設備文件),如果是,則返回 true。 [ -f $file ] 返回 true。
-g file 檢測文件是否設置了 SGID 位,如果是,則返回 true。 [ -g $file ] 返回 false。
-k file 檢測文件是否設置了粘着位(Sticky Bit),如果是,則返回 true。 [ -k $file ] 返回 false。
-p file 檢測文件是否是有管道,如果是,則返回 true。 [ -p $file ] 返回 false。
-u file 檢測文件是否設置了 SUID 位,如果是,則返回 true。 [ -u $file ] 返回 false。
-r file 檢測文件是否可讀,如果是,則返回 true。 [ -r $file ] 返回 true。
-w file 檢測文件是否可寫,如果是,則返回 true。 [ -w $file ] 返回 true。
-x file 檢測文件是否可執行,如果是,則返回 true。 [ -x $file ] 返回 true。
-s file 檢測文件是否為空(文件大小是否大於0),不為空返回 true。 [ -s $file ] 返回 true。
-e file 檢測文件(包括目錄)是否存在,如果是,則返回 true。 [ -e $file ] 返回 true

流程控制

  1. if 語句語法格式:
if condition
then
    command1 
    command2
    ...
    commandN 
fi
  1. if else 語法格式:
if condition
then
    command1 
    command2
    ...
    commandN
else
    command
fi
  1. if else-if else 語法格式:
if condition1
then
    command1
elif condition2 
then 
    command2
else
    commandN
fi

for循環一般格式為:

for var in item1 item2 ... itemN
do
    command1
    command2
    ...
    commandN
done
  1. while 循環一般格式
while condition
do
    command
done
  1. until 循環一般格式
until condition
do
    command
done
  1. case in格式
case 值 in
模式1)
    command1
    command2
    ...
    commandN
    ;;
模式2)
    command1
    command2
    ...
    commandN
    ;;
esac
  1. 跳出循環:
  • break跳出所有循環
  • continue跳出當前循環

函數定義

輸入輸出重定向

  1. 文件描述符
  • 標準輸入 0
  • 標準輸出 1
  • 標準錯誤輸出 2
  1. stdin<file 標準輸入重定向
  2. stdout>file 標準輸出重定向
  3. stderr>>file 標準錯誤輸出重定向,以追加的方式

shell文件包含

  1. .filename
  2. source filename

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

※推薦評價好的iphone維修中心

擁有專業的維修技術團隊,同時聘請資深iphone手機維修專家,現場說明手機問題,快速修理,沒修好不收錢

[源碼解析]為什麼mapPartition比map更高效_貨運

※評比南投搬家公司費用收費行情懶人包大公開

搬家價格與搬家費用透明合理,不亂收費。本公司提供下列三種搬家計費方案,由資深專業組長到府估價,替客戶量身規劃選擇最經濟節省的計費方式

[源碼解析]為什麼mapPartition比map更高效

目錄

  • [源碼解析]為什麼mapPartition比map更高效
    • 0x00 摘要
    • 0x01 map vs mapPartition
      • 1.1 map
      • 1.2 mapPartition
      • 1.3 異同
    • 0x02 代碼
    • 0x03 Flink的傳輸機制
      • 3.1 傳輸機制概述
      • 3.2 遠程通信
      • 3.3 TaskManager進程內傳輸
      • 3.4 源碼分析
    • 0x04 runtime
      • 4.1 Driver
      • 4.2 MapDriver
      • 4.3 MapPartitionDriver
      • 4.4 效率區別
    • 0x05 優化和ChainedMapDriver
    • 0x06 總結
    • 0x07 參考

0x00 摘要

自從函數式編程和響應式編程逐漸進入到程序員的生活之後,map函數作為其中一個重要算子也為大家所熟知,無論是前端web開發,手機開發還是後端服務器開發,都很難逃過它的手心。而在大數據領域中又往往可以見到另外一個算子mapPartition的身影。在性能調優中,經常會被建議盡量用 mappartition 操作去替代 map 操作。本文將從Flink源碼和示例入手,為大家解析為什麼mapPartition比map更高效。

0x01 map vs mapPartition

1.1 map

Map的作用是將數據流上每個元素轉換為另外的元素,比如data.map { x => x.toInt }。它把數組流中的每一個值,使用所提供的函數執行一遍,一一對應。得到與元素個數相同的數組流。然後返回這個新數據流。

1.2 mapPartition

MapPartition的作用是單個函數調用并行分區,比如data.mapPartition { in => in map { (_, 1) } }。該函數將分區作為“迭代器”,可以產生任意數量的結果。每個分區中的元素數量取決於并行度和以前的operations。

1.3 異同

其實,兩者完成的業務操作是一樣的,本質上都是將數據流上每個元素轉換為另外的元素。

區別主要在兩點。

從邏輯實現來講

  • map邏輯實現簡單,就是在函數中簡單一一轉換,map函數的輸入和輸入都是單個元素。
  • mapPartition相對複雜,函數的輸入有兩個,一般格式為 void mapPartition(Iterable<T> values, Collector<O> out) 。其中values是需要映射轉換的所有記錄,out是用來發送結果的collector。具體返回什麼,如何操作out來返回結果,則完全依賴於業務邏輯。

從調用次數來說

  • 數據有多少個元素,map就會被調用多少次。
  • 數據有多少分區,mapPartition就會被調用多少次。

為什麼MapPartition有這麼高效呢,下面我們將具體論證。

0x02 代碼

首先我們給出示例代碼,從下文中我們可以看出,map就是簡單的轉換,而mapPartition則不但要做轉換,程序員還需要手動操作如何返回結果:

public class IteratePi {

    public static void main(String[] args) throws Exception {
        final ExecutionEnvironment env=ExecutionEnvironment.getExecutionEnvironment();
        //迭代次數
        int iterativeNum=10;
        DataSet<Integer> wordList = env.fromElements(1, 2, 3);
      
        IterativeDataSet<Integer> iterativeDataSet=wordList.iterate(iterativeNum);
        DataSet<Integer> mapResult=iterativeDataSet
          			.map(new MapFunction<Integer, Integer>() {
            @Override
            public Integer map(Integer value) throws Exception {
                value += 1;
                return value;
            }
        });
        //迭代結束的條件
        DataSet<Integer> result=iterativeDataSet.closeWith(mapResult);
        result.print();

        MapPartitionOperator<Integer, Integer> mapPartitionResult = iterativeDataSet
                .mapPartition(new MapPartitionFunction<Integer, Integer>() {
            @Override
            public void mapPartition(Iterable<Integer> values, Collector<Integer> out) {
                for (Integer value : values) {
                    // 這裏需要程序員自行決定如何返回,即調用collect操作。
                    out.collect(value + 2);
                }
            }                                                                                                                           					}
        );
        //迭代結束的條件
        DataSet<Integer> partitionResult=iterativeDataSet.closeWith(mapPartitionResult);
        partitionResult.print();
    }
}

0x03 Flink的傳輸機制

世界上很少有沒有來由的愛,也少見免費的午餐。mapPartition之所以高效,其所依賴的基礎就是Flink的傳輸機制。所以我們下面就講解下為什麼。

大家都知道,Spark是用微批處理來模擬流處理,就是說,spark還是一批一批的傳輸和處理數據,所以我們就能理解mapPartition的機制就是基於這一批數據做統一處理。這樣確實可以高效。

但是Flink號稱是純流,即Flink是每來一個輸入record,就進行一次業務處理,然後返回給下游算子。

有的兄弟就會產生疑問:每次都只是處理單個記錄,怎麼能夠讓mapPartition做到批次處理呢。其實這就是Flink的微妙之處:即Flink確實是每次都處理一個輸入record,但是在上下游傳輸時候,Flink還是把records累積起來做批量傳輸的。也可以這麼理解:從傳輸的角度講,Flink是微批處理的

3.1 傳輸機制概述

Flink 的網絡棧是組成 flink-runtime 模塊的核心組件之一,也是 Flink 作業的核心部分。所有來自 TaskManager 的工作單元(子任務)都通過它來互相連接。流式傳輸數據流都要經過網絡棧,所以它對 Flink 作業的性能表現(包括吞吐量和延遲指標)至關重要。與通過 Akka 使用 RPC 的 TaskManager 和 JobManager 之間的協調通道相比,TaskManager 之間的網絡棧依賴的是更底層的,基於 Netty 的 API。

3.2 遠程通信

一個運行的application的tasks在持續交換數據。TaskManager負責做數據傳輸。不同任務之間的每個(遠程)網絡連接將在 Flink 的網絡棧中獲得自己的 TCP 通道。但是如果同一任務的不同子任務被安排到了同一個 TaskManager,則它們與同一個 TaskManager 的網絡連接將被多路復用,並共享一個 TCP 信道以減少資源佔用。

每個TaskManager有一組網絡緩衝池(默認每個buffer是32KB),用於發送與接受數據。如發送端和接收端位於不同的TaskManager進程中,則它們需要通過操作系統的網絡棧進行交流。流應用需要以管道的模式進行數據交換,也就是說,每對TaskManager會維持一個永久的TCP連接用於做數據交換。在shuffle連接模式下(多個sender與多個receiver),每個sender task需要向每個receiver task發送數據,此時TaskManager需要為每個receiver task都分配一個緩衝區。

一個記錄被創建並傳遞之後(例如通過 Collector.collect()),它會被遞交到RecordWriter,其將來自 Java 對象的記錄序列化為一個字節序列,後者最終成為網絡緩存。RecordWriter 首先使用SpanningRecordSerializer將記錄序列化為一個靈活的堆上字節數組。然後它嘗試將這些字節寫入目標網絡通道的關聯網絡緩存。

因為如果逐個發送會降低每個記錄的開銷並帶來更高的吞吐量,所以為了取得高吞吐量,TaskManager的網絡組件首先從緩衝buffer中收集records,然後再發送。也就是說,records並不是一個接一個的發送,而是先放入緩衝,然後再以batch的形式發送。這個技術可以高效使用網絡資源,並達到高吞吐。類似於網絡或磁盤 I/O 協議中使用的緩衝技術。

接收方網絡棧(netty)將接收到的緩存寫入適當的輸入通道。最後(流式)任務的線程從這些隊列中讀取並嘗試在RecordReader的幫助下,通過Deserializer將積累的數據反序列化為 Java 對象。

3.3 TaskManager進程內傳輸

若sender與receiver任務都運行在同一個TaskManager進程,則sender任務會將發送的條目做序列化,並存入一個字節緩衝。然後將緩衝放入一個隊列,直到隊列被填滿。

Receiver任務從隊列中獲取緩衝,並反序列化輸入的條目。所以,在同一個TaskManager內,任務之間的數據傳輸並不經過網絡交互。

在同一個TaskManager進程內,也是批量傳輸

3.4 源碼分析

我們基於Flink優化的結果進行分析驗證,看看Flink是不是把記錄寫入到buffer中,這種情況下運行的是CountingCollector和ChainedMapDriver。

copyFromSerializerToTargetChannel:153, RecordWriter (org.apache.flink.runtime.io.network.api.writer)
emit:116, RecordWriter (org.apache.flink.runtime.io.network.api.writer)
emit:60, ChannelSelectorRecordWriter (org.apache.flink.runtime.io.network.api.writer)
collect:65, OutputCollector (org.apache.flink.runtime.operators.shipping)
collect:35, CountingCollector (org.apache.flink.runtime.operators.util.metrics)
collect:79, ChainedMapDriver (org.apache.flink.runtime.operators.chaining)
collect:35, CountingCollector (org.apache.flink.runtime.operators.util.metrics)
invoke:196, DataSourceTask (org.apache.flink.runtime.operators)
doRun:707, Task (org.apache.flink.runtime.taskmanager)
run:532, Task (org.apache.flink.runtime.taskmanager)
run:748, Thread (java.lang)

當執行完用戶定義的map函數之後,系統運行在 ChainedMapDriver.collect 函數。

※智慧手機時代的來臨,RWD網頁設計為架站首選

網動結合了許多網際網路業界的菁英共同研發簡單易操作的架站工具,及時性的更新,為客戶創造出更多的網路商機。

public void collect(IT record) {
    this.outputCollector.collect(this.mapper.map(record));// mapper就是用戶代碼
}

然後調用到了CountingCollector.collect

public void collect(OUT record) {
		this.collector.collect(record);// record就是用戶轉換后的記錄
}

OutputCollector.collect函數會把記錄發送給所有的writers。

this.delegate.setInstance(record);// 先把record設置到SerializationDelegate中
for (RecordWriter<SerializationDelegate<T>> writer : writers) {  // 所有的writer
   writer.emit(this.delegate); // 發送record
}

RecordWriter負責把數據序列化,然後寫入到緩存中。它有兩個實現類:

  • BroadcastRecordWriter: 維護了多個下游channel,發送數據到下游所有的channel中。
  • ChannelSelectorRecordWriter: 通過channelSelector對象判斷數據需要發往下游的哪個channel。我們用的正是這個RecordWriter

這裏我們分析下ChannelSelectorRecordWriteremit方法:

public void emit(T record) throws IOException, InterruptedException {
   emit(record, channelSelector.selectChannel(record));
}

這裏使用了channelSelector.selectChannel方法。該方法為record尋找到對應下游channel id。

public class OutputEmitter<T> implements ChannelSelector<SerializationDelegate<T>> {
	public final int selectChannel(SerializationDelegate<T> record) {
		switch (strategy) {
		case FORWARD:
			return forward(); // 我們代碼用到了這種情況。這裏 return 0;
    ......
		}
	}
}

接下來我們又回到了父類RecordWriter.emit

protected void emit(T record, int targetChannel) throws IOException, InterruptedException {
   serializer.serializeRecord(record);
   // Make sure we don't hold onto the large intermediate serialization buffer for too long
   if (copyFromSerializerToTargetChannel(targetChannel)) {
      serializer.prune();
   }
}

關鍵的邏輯在於copyFromSerializerToTargetChannel此方法從序列化器中複製數據到目標channel,我們可以看出來,每條記錄都是寫入到buffer中

protected boolean copyFromSerializerToTargetChannel(int targetChannel) throws IOException, InterruptedException {
   // We should reset the initial position of the intermediate serialization buffer before
   // copying, so the serialization results can be copied to multiple target buffers.
   // 此處Serializer為SpanningRecordSerializer
   // reset方法將serializer內部的databuffer position重置為0
   serializer.reset();

   boolean pruneTriggered = false;
    // 獲取目標channel的bufferBuilder
    // bufferBuilder內維護了MemorySegment,即內存片段
    // Flink的內存管理依賴MemorySegment,可實現堆內堆外內存的管理
    // RecordWriter內有一個bufferBuilder數組,長度和下游channel數目相同
    // 該數組以channel ID為下標,存儲和channel對應的bufferBuilder
    // 如果對應channel的bufferBuilder尚未創建,調用requestNewBufferBuilder申請一個新的bufferBuilder  
   BufferBuilder bufferBuilder = getBufferBuilder(targetChannel);
    // 複製serializer的數據到bufferBuilder中
   SerializationResult result = serializer.copyToBufferBuilder(bufferBuilder);
    // 循環直到result完全被寫入到buffer
    // 一條數據可能會被寫入到多個緩存中
    // 如果緩存不夠用,會申請新的緩存
    // 數據完全寫入完畢之時,當前正在操作的緩存是沒有寫滿的
    // 因此返回true,表明需要壓縮該buffer的空間  
   while (result.isFullBuffer()) {
      finishBufferBuilder(bufferBuilder);

      // If this was a full record, we are done. Not breaking out of the loop at this point
      // will lead to another buffer request before breaking out (that would not be a
      // problem per se, but it can lead to stalls in the pipeline).
      if (result.isFullRecord()) {
         pruneTriggered = true;
         emptyCurrentBufferBuilder(targetChannel);
         break;
      }

      bufferBuilder = requestNewBufferBuilder(targetChannel);
      result = serializer.copyToBufferBuilder(bufferBuilder);
   }
   checkState(!serializer.hasSerializedData(), "All data should be written at once");

   // 如果buffer超時時間為0,需要flush目標channel的數據
   if (flushAlways) {
      flushTargetPartition(targetChannel);
   }
   return pruneTriggered;
}

0x04 runtime

4.1 Driver

Driver是Flink runtime的一個重要概念,是在一個task中運行的用戶業務邏輯組件,具體實現了批量操作代碼。其內部API包括初始化,清除,運行,取消等邏輯。

public interface Driver<S extends Function, OT> {
   ......
   void setup(TaskContext<S, OT> context);
   void run() throws Exception;
   void cleanup() throws Exception;
   void cancel() throws Exception;
}

具體在 org.apache.flink.runtime.operators 目錄下,我們能夠看到各種Driver的實現,基本的算子都有自己的Driver。

......
CoGroupDriver.java
FlatMapDriver.java
FullOuterJoinDriver.java
GroupReduceCombineDriver.java
GroupReduceDriver.java
JoinDriver.java
LeftOuterJoinDriver.java
MapDriver.java
MapPartitionDriver.java
......

4.2 MapDriver

map算子對應的就是MapDriver。

結合上節我們知道,上游數據是通過batch方式批量傳入的。所以,在run函數會遍歷輸入,每次取出一個record,然後調用用戶自定義函數function.map對這個record做map操作。

public class MapDriver<IT, OT> implements Driver<MapFunction<IT, OT>, OT> {

   @Override
   public void run() throws Exception {
      final MutableObjectIterator<IT> input = this.taskContext.getInput(0);
      .....
      else {
         IT record = null;
        
         // runtime主動進行循環,這樣導致大量函數調用
         while (this.running && ((record = input.next()) != null)) {
            numRecordsIn.inc();
            output.collect(function.map(record)); // function是用戶函數
         }
      }
   }
}

4.3 MapPartitionDriver

MapPartitionDriver是mapPartition的具體組件。系統會把得到的批量數據inIter一次性的都傳給用戶自定義函數,由用戶代碼來進行遍歷操作

public class MapPartitionDriver<IT, OT> implements Driver<MapPartitionFunction<IT, OT>, OT> {
   @Override
   public void run() throws Exception {
     
		final MutableObjectIterator<IT> input = new CountingMutableObjectIterator<>(this.taskContext.<IT>getInput(0), numRecordsIn);     
      ......
      } else {
         final NonReusingMutableToRegularIteratorWrapper<IT> inIter = new NonReusingMutableToRegularIteratorWrapper<IT>(input, this.taskContext.<IT>getInputSerializer(0).getSerializer());

         // runtime不參与循環,這樣可以減少函數調用
         function.mapPartition(inIter, output);
      }
   }
}

4.4 效率區別

我們能夠看到map和mapPartition的input都是MutableObjectIterator input類型, 說明兩者的輸入一致。只不過map是在Driver代碼中進行循環,mapPartition在用戶代碼中進行循環。具體mapPartition的 效率提高體現在如下方面 :

  1. 假設一共有60個數據需要轉換,map會在runtime中調用用戶函數60次。
  2. runtime把數據分成6個partition操作,則mapPartition在runtime中會調用用戶函數6次,在每個用戶函數中分別循環10次。對於runtime來說,map操作會多出54次用戶函數調用。
  3. 如果用戶業務中需要頻繁創建額外的對象或者外部資源操作,mapPartition的優勢更可以體現。 例如將數據寫入Mysql, 那麼map需要為每個元素創建一個數據庫連接,而mapPartition為每個partition創建一個鏈接。

假設有上億個數據需要map,這資源佔用和運行速度效率差別會相當大。

0x05 優化和ChainedMapDriver

之前提到了優化,這裏我們再詳細深入下如何優化map算子。

Flink有一個關鍵的優化技術稱為任務鏈,用於(在某些情況下)減少本地通信的過載。為了滿足任務鏈的條件,至少兩個以上的operator必須配置為同一併行度,並且使用本地向前的(local forwad)方式連接。任務鏈可以被認為是一種管道。

當管道以任務鏈的方式執行時候,Operators的函數被融合成單個任務,並由一個單獨的線程執行。一個function產生的records,通過使用一個簡單的方法調用,被遞交給下一個function。所以這裡在方法之間的records傳遞中,基本沒有序列化以及通信消耗

針對優化后的Operator Chain,runtime對應的Driver則是ChainedMapDriver。這是通過 MAP(MapDriver.class, ChainedMapDriver.class, PIPELINED, 0), 映射得到的。

我們可以看到,因為是任務鏈,所以每個record是直接在管道中流淌 ,ChainedMapDriver連循環都省略了,直接map轉換后丟給下游去也

public class ChainedMapDriver<IT, OT> extends ChainedDriver<IT, OT> {

   private MapFunction<IT, OT> mapper; // 用戶函數

   @Override
   public void collect(IT record) {
      try {
         this.numRecordsIn.inc();
         this.outputCollector.collect(this.mapper.map(record));
      } catch (Exception ex) {
         throw new ExceptionInChainedStubException(this.taskName, ex);
      }
   }
}

// 這時的調用棧如下
map:23, UserFunc$1 (com.alibaba.alink)
collect:79, ChainedMapDriver (org.apache.flink.runtime.operators.chaining)
collect:35, CountingCollector (org.apache.flink.runtime.operators.util.metrics)
invoke:196, DataSourceTask (org.apache.flink.runtime.operators)
doRun:707, Task (org.apache.flink.runtime.taskmanager)
run:532, Task (org.apache.flink.runtime.taskmanager)
run:748, Thread (java.lang)

0x06 總結

map和mapPartition實現的基礎是Flink的數據傳輸機制 :Flink確實是每次都處理一個輸入record,但是在上下游之間傳輸時候,Flink還是把records累積起來做批量傳輸。即可以認為從數據傳輸模型角度講,Flink是微批次的。

對於數據流轉換,因為是批量傳輸,所以對於積累的records,map是在runtime Driver代碼中進行循環,mapPartition在用戶代碼中進行循環。

map的函數調用次數要遠高於mapPartition。如果在用戶函數中涉及到頻繁創建額外的對象或者外部資源操作,則mapPartition性能遠遠高出。

如果沒有connection之類的操作,則通常性能差別並不大,通常不會成為瓶頸,也沒有想象的那麼嚴重。

0x07 參考

深入了解 Flink 網絡棧 ——A Deep-Dive into Flink’s Network Stack

Flink架構(二)- Flink中的數據傳輸

Flink 源碼之節點間通信

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

※回頭車貨運收費標準

宇安交通關係企業,自成立迄今,即秉持著「以誠待人」、「以實處事」的企業信念

深入理解JS:var、let、const的異同_網頁設計公司

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

透過選單樣式的調整、圖片的縮放比例、文字的放大及段落的排版對應來給使用者最佳的瀏覽體驗,所以不用擔心有手機版網站兩個後台的問題,而視覺效果也是透過我們前端設計師優秀的空間比例設計,不會因為畫面變大變小而影響到整體視覺的美感。

目錄

  • 序言
  • var 與 let 的區別
    • 作用域
    • 重複聲明
    • 綁定全局對象
    • 變量提升與暫存死區
  • let 與 const 異同
  • 參考

1.序言

var、let 和 const 都是 JavaScript 中用來聲明變量的關鍵字,並且 let 和 const 關鍵字是在 ES6 中才新增的。既然都是用來聲明變量的,那它們之間有什麼區別呢?讓我們來一探究竟。

2.var 與 let 的區別

(1)作用域

用 var 聲明的變量的作用域是它當前的執行上下文,即如果是在任何函數外面,則是全局執行上下文,如果在函數裏面,則是當前函數執行上下文。換句話說,var 聲明的變量的作用域只能是全局或者整個函數塊的。

而 let 聲明的變量的作用域則是它當前所處代碼塊,即它的作用域既可以是全局或者整個函數塊,也可以是 if、while、switch等用{}限定的代碼塊。

另外,var 和 let 的作用域規則都是一樣的,其聲明的變量只在其聲明的塊或子塊中可用。

示例代碼:

function varTest() {
  var a = 1;

  {
    var a = 2; // 函數塊中,同一個變量
    console.log(a); // 2
  }

  console.log(a); // 2
}

function letTest() {
  let a = 1;

  {
    let a = 2; // 代碼塊中,新的變量
    console.log(a); // 2
  }

  console.log(a); // 1
}

varTest();
letTest();

從上述示例中可以看出,let 聲明的變量的作用域可以比 var 聲明的變量的作用域有更小的限定範圍,更具靈活。

(2)重複聲明

var 允許在同一作用域中重複聲明,而 let 不允許在同一作用域中重複聲明,否則將拋出異常。

var 相關示例代碼:

var a = 1;
var a = 2;

console.log(a) // 2

function test() {
  var a = 3;
  var a = 4;
  console.log(a) // 4
}

test()

let 相關示例代碼:

if(false) {
  let a = 1;
  let a = 2; // SyntaxError: Identifier 'a' has already been declared
}
switch(index) {
  case 0:
    let a = 1;
  break;

  default:
    let a = 2; // SyntaxError: Identifier 'a' has already been declared
    break;
}

從上述示例中可以看出,let 聲明的重複性檢查是發生在詞法分析階段,也就是在代碼正式開始執行之前就會進行檢查。

(3)綁定全局對象

var 在全局環境聲明變量,會在全局對象里新建一個屬性,而 let 在全局環境聲明變量,則不會在全局對象里新建一個屬性。

示例代碼:

var foo = 'global'
let bar = 'global'

console.log(this.foo) // global
console.log(this.bar) // undefined

那這裏就一個疑問, let 在全局環境聲明變量不在全局對象的屬性中,那它是保存在哪的呢?

var foo = 'global'
let bar = 'global'

function test() {}

console.dir(test)

在Chrome瀏覽器的控制台中,通過執行上述代碼,查看 test 函數的作用域鏈,其結果如圖:

由上圖可知,let 在全局環境聲明變量 bar 保存在[[Scopes]][0]: Script這個變量對象的屬性中,而[[Scopes]][1]: Global就是我們常說的全局對象。

(4)變量提升與暫存死區

var 聲明變量存在變量提升,如何理解變量提升呢?

要解釋清楚這個,就要涉及到執行上下文和變量對象。

在 JavaScript 代碼運行時,解釋執行全局代碼、調用函數或使用 eval 函數執行一個字符串表達式都會創建並進入一個新的執行環境,而這個執行環境被稱之為執行上下文。因此執行上下文有三類:全局執行上下文、函數執行上下文、eval 函數執行上下文。

執行上下文可以理解為一個抽象的對象,如下圖:

Variable object:變量對象,用於存儲被定義在執行上下文中的變量 (variables) 和函數聲明 (function declarations) 。

Scope chain:作用域鏈,是一個對象列表 (list of objects) ,用以檢索上下文代碼中出現的標識符 (identifiers) 。

thisValue:this 指針,是一個與執行上下文相關的特殊對象,也被稱之為上下文對象。

一個執行上下文的生命周期可以分為三個階段:創建、執行、釋放。如下圖:

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

搬家費用:依消費者運送距離、搬運樓層、有無電梯、步行距離、特殊地形、超重物品等計價因素後,評估每車次單

而所有使用 var 聲明的變量都會在執行上下文的創建階段時作為變量對象的屬性被創建並初始化,這樣才能保證在執行階段能通過標識符在變量對象里找到對應變量進行賦值操作等。

而用 var 聲明的變量構建變量對象時進行的操作如下:

  • 由名稱和對應值(undefined)組成一個變量對象的屬性被創建(創建並初始化)
  • 如果變量名稱跟已經聲明的形式參數或函數相同,則變量聲明不會幹擾已經存在的這類屬性。

上述過程就是我們所謂的“變量提升”,這也就能解釋為什麼變量可以在聲明之前使用,因為使用是在執行階段,而在此之前的創建階段就已經將聲明的變量添加到了變量對象中,所以執行階段通過標識符可以在變量對象中查找到,也就不會報錯。

示例代碼:

console.log(a) // undefined

var a = 1;

console.log(a) // 1

let 聲明變量存在暫存死區,如何理解暫存死區呢?

其實 let 也存在與 var 類似的“變量提升”過程,但與 var 不同的是其在執行上下文的創建階段,只會創建變量而不會被初始化(undefined),並且 ES6 規定了其初始化過程是在執行上下文的執行階段(即直到它們的定義被執行時才初始化),使用未被初始化的變量將會報錯。

let and const declarations define variables that are scoped to the running execution context’s LexicalEnvironment. The variables are created when their containing Lexical Environment is instantiated but may not be accessed in any way until the variable’s LexicalBinding is evaluated. A variable defined by a LexicalBinding with an Initializer is assigned the value of its Initializer’s AssignmentExpression when the LexicalBinding is evaluated, not when the variable is created. If a LexicalBinding in a let declaration does not have an Initializer the variable is assigned the value undefined when the LexicalBinding is evaluated.

在變量初始化前訪問該變量會導致 ReferenceError,因此從進入作用域創建變量,到變量開始可被訪問的一段時間(過程),就稱為暫存死區(Temporal Dead Zone)。

示例代碼 1:

console.log(bar); // undefined
console.log(foo); // ReferenceError: foo is not defined

var bar = 1;
let foo = 2;

示例代碼 2:

var foo = 33;
{
  let foo = (foo + 55); // ReferenceError: foo is not defined
}

注:首先,需要分清變量的創建、初始化、賦值是三個不同的過程。另外,從 ES5 開始用詞法環境(Lexical Environment)替代了 ES3 中的變量對象(Variable object)來管理靜態作用域,但作用是相同的。為了方便理解,上述講解中仍保留使用變量對象來進行描述。

小結

  1. var 聲明的變量在執行上下文創建階段就會被「創建」和「初始化」,因此對於執行階段來說,可以在聲明之前使用。

  2. let 聲明的變量在執行上下文創建階段只會被「創建」而不會被「初始化」,因此對於執行階段來說,如果在其定義執行前使用,相當於使用了未被初始化的變量,會報錯。

3.let 與 const 異同

const 與 let 很類似,都具有上面提到的 let 的特性,唯一區別就在於 const 聲明的是一個只讀變量,聲明之後不允許改變其值。因此,const 一旦聲明必須初始化,否則會報錯。

示例代碼:

let a;
const b = "constant"

a = "variable"
b = 'change' // TypeError: Assignment to constant variable

如何理解聲明之後不允許改變其值?

其實 const 其實保證的不是變量的值不變,而是保證變量指向的內存地址所保存的數據不允許改動(即棧內存在的值和地址)。

JavaScript 的數據類型分為兩類:原始值類型和對象(Object類型)。

對於原始值類型(undefined、null、true/false、number、string),值就保存在變量指向的那個內存地址(在棧中),因此 const 聲明的原始值類型變量等同於常量。

對於對象類型(object,array,function等),變量指向的內存地址其實是保存了一個指向實際數據的指針,所以 const 只能保證指針是不可修改的,至於指針指向的數據結構是無法保證其不能被修改的(在堆中)。

示例代碼:

const obj = {
  value: 1
}

obj.value = 2

console.log(obj) // { value: 2 }

obj = {} // TypeError: Assignment to constant variable

4.參考

var – JavaScript | MDN

let – JavaScript – MDN – Mozilla

const – JavaScript – MDN – Mozilla

深入理解JavaScript系列(12):變量對象(Variable Object)

ES6 let 與 const

詳解ES6暫存死區TDZ

嗨,你知道 let 和 const 嗎?

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

節能減碳愛地球是景泰電動車的理念,是創立景泰電動車行的初衷,滿意態度更是服務客戶的最高品質,我們的成長來自於你的推薦。

洞悉MySQL底層架構:遊走在緩衝與磁盤之間_台中搬家

台中搬家公司費用怎麼算?

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

提起MySQL,其實網上已經有一大把教程了,為什麼我還要寫這篇文章呢,大概是因為網上很多網站都是比較零散,而且描述不夠直觀,不能系統對MySQL相關知識有一個系統的學習,導致不能形成知識體系。為此我撰寫了這篇文章,試圖讓這些底層架構相關知識更加直觀易懂:

  • 盡量以圖文的方式描述技術原理;
  • 涉及到關鍵的技術,附加官網或者技術書籍來源,方便大家進一步擴展學習;
  • 涉及到的背景知識盡可能做一個交代,比如討論到log buffer的刷盤方式,延伸一下IO寫磁盤相關知識點。

好了,MySQL從不會到精通系列馬上就要開始了(看完之後還是不會的話..請忽略這句話)。

可能會有同學問:為啥不直接學更加先進的TiDB,或者是強大的OceanBase。

其實,MySQL作為老牌的應用場景廣泛的關係型開源數據庫,其底層架構是很值得我們學習的,吸收其設計精華,那麼我們在平時的方案設計工作中也可以借鑒,如果項目中用的是MySQL,那麼就能夠把數據庫用的更好了,了解了MySQL底層的執行原理,對於調優工作也是有莫大幫助的。本文我重點講述MySQL底層架構,涉及到:

  • 內存結構buffer poollog bufferchange buffer,buffer pool的頁淘汰機制是怎樣的;
  • 磁盤結構系統表空間獨立表空間通用表空間undo表空間redo log
  • 以及IO相關底層原理、查詢SQL執行流程、數據頁結構行結構描述、聚集索引輔助索引的底層數據組織方式、MVCC多版本併發控制的底層實現原理,以及可重複讀讀已提交是怎麼通過MVCC實現的。

看完文本文,您將了解到:

  1. 整體架構:InnoDB存儲架構是怎樣的 (1、MySQL架構)
  2. 工作原理:查詢語句的底層執行流程是怎樣的 (2、查詢SQL執行流程)
  3. IO性能:文件IO操作寫磁盤有哪幾種方式,有什麼IO優化方式 (3.1.2、關於磁盤IO的方式)
  4. 緩存:InnoDB緩存(buffer pool, log buffer)的刷新方式有哪些(3.1.2.2、innodb_flush_method)
  5. 緩存:log buffer是在什麼時候寫入到磁盤的(3.10.2、如何保證數據不丟失 – 其中第四步log buffer持久化到磁盤的時機為)
  6. 緩存:為什麼redo log prepare狀態也要寫磁盤?(3.10.2、如何保證數據不丟失 – 為什麼第二步redo log prepare狀態也要寫磁盤?)
  7. 緩存:臟頁寫盤一般發生在什麼時候(3.10.2、如何保證數據不丟失 – 其中第五步:臟頁刷新到磁盤的時機為)
  8. 緩存:為什麼唯一索引的更新不可以藉助change buffer(3.2、Change Buffer)
  9. 緩存:log buffer的日誌刷盤控制參數innodb_flush_log_at_trx_commit對寫性能有什麼影響(3.4.1、配置參數)
  10. 緩存:buffer pool的LRU是如何實現的,為什麼要這樣實現(3.1.1、緩衝池LRU算法)
  11. 表存儲:系統表空間的結構,MySQL InnoDB磁盤存儲格式,各種表空間(系統表空間,獨立表空間,通用表空間)的作用和優缺點是什麼,ibdataibdfrm文件分別是幹嘛的(3.5、表空間)
  12. 行字段存儲:底層頁和行的存儲格式(3.6、InnoDB底層邏輯存儲結構)
  13. 行字段存儲:varcharnull底層是如何存儲的,最大可用存儲多大的長度(3.6.3.1、MySQL中varchar最大長度是多少)
  14. 行字段存儲:行記錄太長了,一頁存不下,該怎麼存儲?(3.6.3.2、行記錄超過頁大小如何存儲)
  15. 索引:數據庫索引的組織方式是怎樣的,明白為什麼要採用B+樹,而不是哈希表、二叉樹或者B樹(3.7、索引 – 為什麼MySQL使用B+樹)
  16. 索引:索引組織方式是怎樣的,為什麼大字段會影響表性能(查詢性能,更新性能)(3.7、索引)
  17. 索引:覆蓋索引聯合索引什麼情況下會生效(3.7.2、輔助索引)
  18. 索引:什麼是索引下推,索引下推減少了哪方面的開銷?(3.7.2、輔助索引 – 索引條件下推)
  19. 索引:Change Buffer對二級索引DML語句有什麼優化(3.2、Change Buffer)
  20. 數據完整性:MySQL是如何保證數據完整性的,redo logundo logbuffer pool數據完整性的關鍵作用分別是什麼(3.10.2、如何保證數據不丟失)
  21. MVCC:MVCC底層是怎麼實現的,可重複讀和讀已提交是怎麼實現的(3.11.2、MVCC實現原理)
  22. 雙寫緩衝區有什麼作用(3.9、Doublewrite Buffer)
  23. Redo Log在一個事務中是在什麼時候寫入的?binlog和Redo Log有什麼區別?(3.10.1、Redo Log在事務中的寫入時機)

1、MySQL架構

如下圖為MySQL架構涉及到的常用組件:

2、查詢SQL執行流程

有如下錶格:

我們執行以下sql:

select * from t_user where user_id=10000;

2.1、MySQL客戶端與服務器建立連接

如下圖,建立過程:

  • 客戶端通過mysql命令發起連接請求;
  • 經過三次握手后與服務端建立TCP連接;
  • 連接器接收到請求之後使用用戶密碼進行身份驗證;
  • 驗證通過之後,獲取用戶的權限信息緩存起來,該連接後面都是基於該緩存中的權限執行sql

對於Java應用程序來說,一般會把建立好的連接放入數據庫連接池中進行復用,只要這個連接不關閉,就會一直在MySQL服務端保持着,可以通過show processlist命令查看,如下:

注意,這裡有個Time,表示這個連接多久沒有動靜了,上面例子是656秒沒有動靜,默認地,如果超過8個小時還沒有動靜,連接器就會自動斷開連接,可以通過wait_timeout參數進行控制。

2.2、執行SQL

如下圖,執行sql:

  • 服務端接收到客戶端的查詢sql之後,先嘗試從查詢緩存中查詢該sql是否已經有緩存的結果了,如果有則直接返回結果,如果沒有則執行下一步;
  • 分析器拿到sql之後會嘗試對sql語句進行詞法分析和語法分析,校驗語法的正確性,通過之後繼續往下執行;
  • 優化器拿到分析器的sql之後,開始繼續解析sql,判斷到需要走什麼索引,根據實際情況重寫sql,最終生成執行計劃;
  • 執行器根據執行計劃執行sql,執行之前會先進行操作權限校驗;然後根據表存儲引擎調用對飲接口進行查詢數據,這裏的掃描行數就是指的接口返回的記錄數,執行器拿到返回記錄之後進一步加工,如本例子:
    • 執行器拿到select * from t_user where user_id=10000的所有記錄,在依次判斷user_name是不是等於”arthinking”,獲取到匹配的記錄。

3、InnoDB引擎架構

如下圖,為存儲引擎的架構:

其實內存中的結構不太好直接觀察到,不過磁盤的還是可以看到的,我們找到磁盤中MySQL的數據文件夾看看:

cd innodb_data_home_dir 查看MySQL 數據目錄:

|- ib_buffer_pool  // 保存緩衝池中頁面的表空間ID和頁面ID,用於重啟恢復緩衝池
|- ib_logfile0  // redo log 磁盤文件1
|- ib_logfile1  // redo log 磁盤文件2,默認情況下,重做日誌存在磁盤的這兩個文件中,循環的方式寫入重做日誌
|- ibdata1  // 系統表空間文件
|- ibtmp1  // 默認臨時表空間文件,可通過innodb_temp_data_file_path屬性指定文件位置
|- mysql/
|- mysql-bin.000001  // bin log文件
|- mysql-bin.000001  // bin log文件
...
|- mysql-bin.index  // bin log文件索引
|- mysqld.local.err  // 錯誤日誌
|- mysqld.local.pid  // mysql進程號
|- performance_schema/  // performance_schema數據庫
|- sys/  // sys數據庫
|- test/  // 數據庫文件夾
    |- db.opt  // test數據庫配置文件,包含數據庫字符集屬性
    |- t.frm  // 數據表元數據文件,不管是使用獨立表空間還是系統表空間,每個表都對應有一個
    |- t.ibd  // 數據庫表獨立表空間文件,如果使用的是獨立表空間,則一個表對應一個ibd文件,否則保存在系統表空間文件中

innodb_data_home_dir[1]

ib_buffer_pool[2]

ib_logfile0[3]

ibtmp1[4]

db.opt[5]

接下來我們逐一來介紹。

3.1、buffer pool

buffer pool緩衝池)是主內存中的一個區域,在InnoDB訪問表數據索引數據的時候,會順便把對應的數據頁緩存到緩衝池中。如果直接從緩衝池中直接讀取數據將會加快處理速度。在專用服務器上,通常將80%左右的物理內存分配給緩衝池。

為了提高緩存管理效率,緩衝池把頁面鏈接為列表,使用改進版的LRU算法將很少使用的數據從緩存中老化淘汰掉。

3.1.1、緩衝池LRU算法

通過使用改進版的LRU算法來管理緩衝池列表。

當需要把新頁面存儲到緩衝池中的時候,將淘汰最近最少使用的頁面,並將新頁面添加到舊子列表的頭部。

該算法運行方式:

  • 默認 3/8緩衝池用於舊子列表;
  • 當新頁面如緩衝池時,首先將其插入舊子列表頭部
  • 重複訪問舊子列表的頁面,將使其移動至新子列表的頭部;
  • 隨着數據庫的運行,頁面逐步移至列表尾部,緩衝池中未被方位的頁面最終將被老化淘汰。

相關優化參數:

  • innodb_old_blocks_pct:控制LRU列表中舊子列表的百分比,默認是37,也就是3/8,可選範圍為5~95;
  • innodb_old_blocks_time :指定第一次訪問頁面后的時間窗口,該時間窗口內訪問頁面不會使其移動到LRU列表的最前面。默認是1000,也就是1秒。

innodb_old_blocks_time很重要,有了這1秒,對於全表掃描,由於是順序掃描的,一般同一個數據頁的數據都是在一秒內訪問完成的,不會升級到新子列表中,一直在舊子列表淘汰數據,所以不會影響到新子列表的緩存。

3.1.2、關於磁盤IO的方式

O_DIRECTinnodb_flush_method參數的一個可選值。

這裏先介紹下和數據庫性能密切相關的文件IO操作方法

3.1.2.1、文件IO操作方法

數據庫系統是基於文件系統的,其性能和設備讀寫的機制有密切的關係。

open:打開文件[6]
int open(const char *pathname, int flags);

系統調用Open會為該進程一個文件描述符fd,常用的flags如下:

  • O_WRONLY:表示我們以”寫”的方式打開,告訴內核我們需要向文件中寫入數據;
  • O_DSYNC:每次write都等待物理I/O完成,但是如果寫操作不影響讀取剛寫入的數據,則不等待文件屬性更新;
  • O_SYNC:每次write都等到物理I/O完成,包括write引起的文件屬性的更新;
  • O_DIRECT:執行磁盤IO時繞過緩衝區高速緩存(內核緩衝區),從用戶空間直接將數據傳遞到文件或磁盤設備,稱為直接IO(direct IO)。因為沒有了OS cache,所以會O_DIRECT降低文件的順序讀寫的效率。
write:寫文件[7]
ssize_t write(int fd, const void *buf, size_t count);

使用open打開文件獲取到文件描述符之後,可以調用write函數來寫文件,具體表現根據open函數參數的不同而不同弄。

fsync & fdatasync:刷新文件[8]
#include <unistd.h>

int fsync(int fd);

int fdatasync(int fd);
  • fdatasync:操作完write之後,我們可以調用fdatasync將文件數據塊flush到磁盤,只要fdatasync返回成功,則可以認為數據已經寫到磁盤了;
  • fsync:與O_SYNC參數類似,fsync還會更新文件metadata到磁盤;
  • sync:sync只是將修改過的塊緩衝區寫入隊列,然後就返回,不等實際寫磁盤操作完成;

為了保證文件更新成功持久化到硬盤,除了調用write方法,還需要調用fsync。

大致交互流程如下圖:

更多關於磁盤IO的相關內容,可以閱讀:On Disk IO, Part 1: Flavors of IO[9]

fsync性能問題:除了刷臟頁到磁盤,fsync還會同步文件metadata,而文件數據和metadata通常存放在磁盤不同地方,所以fsync至少需要兩次IO操作。

對fsync性能的優化建議:由於以上性能問題,如果能夠減少metadata的更新,那麼就可以使用fdatasync了。因此需要確保文件的尺寸在write前後沒有發生變化。為此,可以創建固定大小的文件進行寫,寫完則開啟新的文件繼續寫。

3.1.2.2、innodb_flush_method

innodb_flush_method定義用於將數據刷新到InnoDB數據文件和日誌文件的方法,這可能會影響I/O吞吐量。

以下是具體參數說明:

屬性
命令行格式 –innodb-flush-method=value
系統變量 innodb_flush_method
範圍 全局
默認值(Windows) unbuffered
默認值(Unix) fsync
有效值(Windows) unbuffered, normal
有效值(Unix) fsync, O_DSYNC, littlesync, nosync, O_DIRECT, O_DIRECT_NO_FSYNC

比較常用的是這三種:

fsync

默認值,使用fsync()系統調用來flush數據文件和日誌文件到磁盤;

O_DSYNC

由於open函數的O_DSYNC參數在許多Unix系統上都存中問題,因此InnoDB不直接使用O_DSYNC。

InnoDB用於O_SYNC 打開和刷新日誌文件,fsync()刷新數據文件。

表現為:寫日誌操作是在write函數完成,數據文件寫入是通過fsync()系統調用來完成;

O_DIRECT

使用O_DIRECT (在Solaris上對應為directio())打開數據文件,並用於fsync()刷新數據文件和日誌文件。此選項在某些GNU/Linux版本,FreeBSD和Solaris上可用。

表現為:數據文件寫入直接從buffer pool到磁盤,不經過操作系統緩衝,日誌還是需要經過操作系統緩存;

O_DIRECT_NO_FSYNC

在刷新I/O期間InnoDB使用O_DIRECT,並且每次write操作后跳過fsync()系統調用。

此設置適用於某些類型的文件系統,但不適用於其他類型的文件系統。例如,它不適用於XFS。如果不確定所使用的文件系統是否需要fsync()(例如保留所有文件元數據),請改用O_DIRECT。

如下圖所示:

為什麼使用了O_DIRECT配置后還需要調用fsync()?

參考MySQL的這個bug:Innodb calls fsync for writes with innodb_flush_method=O_DIRECT[10]

Domas進行的一些測試表明,如果沒有fsync,某些文件系統(XFS)不會同步元數據。如果元數據會更改,那麼您仍然需要使用fsync(或O_SYNC來打開文件)。

例如,如果在啟用O_DIRECT的情況下增大文件大小,它仍將寫入文件的新部分,但是由於元數據不能反映文件的新大小,因此如果此刻系統發生崩潰,文件尾部可能會丟失。

為此:當重要的元數據發生更改時,請繼續使用fsync或除O_DIRECT之外,也可以選擇使用O_SYNC。

MySQL從v5.6.7起提供了O_DIRECT_NO_FSYNC選項來解決此類問題。

3.2、Change Buffer

change buffer是一種特殊的數據結構,當二級索引頁(非唯一索引)不在緩衝池中時,它們會緩存這些更改 。當頁面通過其他讀取操作加載到緩衝池中時,再將由INSERTUPDATEDELETE操作(DML)產生的change buffer合併到buffer pool的數據頁中。

為什麼唯一索引不可以使用chage buffer?

針對唯一索引,如果buffer pool不存在對應的數據頁,還是需要先去磁盤加載數據頁,才能判斷記錄是否重複,這一步避免不了。

而普通索引是非唯一的,插入的時候以相對隨機的順序發生,刪除和更新也會影響索引樹中不相鄰的二級索引樹,通過使用合併緩衝,避免了在磁盤產生大量的隨機IO訪問獲取普通索引頁。

問題

當有許多受影響的行和許多輔助索引要更新時,change buffer合併可能需要幾個小時,在此期間,I/O會增加,可能會導致查詢效率大大降低,即使在事務提交之後,或者服務器重啟之後,change buffer合併操作也會繼續發生。相關閱讀:Section 14.22.2, “Forcing InnoDB Recovery”

3.3、自適應哈希索引

自適應哈希索引功能由innodb_adaptive_hash_index變量啟用 ,或在服務器啟動時由--skip-innodb-adaptive-hash-index禁用。

3.4、Log Buffer

log buffer(日誌緩衝區)用於保存要寫入磁盤上的log file(日誌文件)的數據。日誌緩存區的內容會定期刷新到磁盤。

日誌緩衝區大小由innodb_log_buffer_size變量定義 。默認大小為16MB。較大的日誌緩衝區可以讓大型事務在提交之前無需將redo log寫入磁盤。

如果您有更新,插入或者刪除多行的事務,嘗試增大日誌緩衝區的大小可以節省磁盤I/O。

3.4.1、配置參數

innodb_flush_log_at_trx_commit

innodb_flush_log_at_trx_commit 變量控制如何將日誌緩衝區的內容寫入並刷新到磁盤。

該參數控制是否嚴格存儲ACID還是嘗試獲取更高的性能,可以通過該參數獲取更好的性能,但是會導致在系統崩潰的過程中導致數據丟失。

可選參數:

  • 0,事務提交之後,日誌只記錄到log buffer中,每秒寫一次日誌到緩存並刷新到磁盤,尚未刷新的日誌可能會丟失;
  • 1,要完全符合ACID,必須使用該值,表示日誌在每次事務提交時寫入緩存並刷新到磁盤;
  • 2,每次事務提交之後,日誌寫到page cache,每秒刷一次到磁盤,尚未刷新的日誌可能會丟失;

innodb_flush_log_at_timeout

innodb_flush_log_at_timeout 變量控制日誌刷新頻率。可讓您將日誌刷新頻率設置為N秒(其中N1 ... 2700,默認值為1)

為了保證數據不丟失,請執行以下操作:

  • 如果啟用了binlog,則設置:sync_binlog=1;
  • innodb_flush_log_at_trx_commit=1;

配置效果如下圖所示:

3.5、表空間

一個InnoDB表及其索引可以在建在系統表空間中,或者是在一個 獨立表空間 中,或在 通用表空間。

  • innodb_file_per_table啟用時,通常是將表存放在獨立表空間中,這是默認配置;
  • innodb_file_per_table禁用時,則會在系統表空間中創建表;
  • 要在通用表空間中創建表,請使用 CREATE TABLE ... TABLESPACE語法。有關更多信息,請參見官方文檔 14.6.3.3 General Tablespaces。

表空間概覽圖:

表空間涉及的文件

相關文件默認在磁盤中的innodb_data_home_dir目錄下:

|- ibdata1  // 系統表空間文件
|- ibtmp1  // 默認臨時表空間文件,可通過innodb_temp_data_file_path屬性指定文件位置
|- test/  // 數據庫文件夾
    |- db.opt  // test數據庫配置文件,包含數據庫字符集屬性
    |- t.frm  // 數據表元數據文件,不管是使用獨立表空間還是系統表空間,每個表都對應有一個
    |- t.ibd  // 數據庫表獨立表空間文件,如果使用的是獨立表空間,則一個表對應一個ibd文件,否則保存在系統表空間文件中

frm文件

創建一個InnoDB表時,MySQL 在數據庫目錄中創建一個.frm文件。frm文件包含MySQL表的元數據(如表定義)。每個InnoDB表都有一個.frm文件。

與其他MySQL存儲引擎不同, InnoDB它還在系統表空間內的自身內部數據字典中編碼有關表的信息。MySQL刪除表或數據庫時,將刪除一個或多個.frm文件以及InnoDB數據字典中的相應條目。

因此,在InnoDB中,您不能僅通過移動.frm 文件來移動表。有關移動InnoDB 表的信息,請參見官方文檔14.6.1.4 Moving or Copying InnoDB Tables。

ibd文件

對於在獨立表空間創建的表,還會在數據庫目錄中生成一個 .ibd表空間文件。

通用表空間中創建的表在現有的常規表空間 .ibd文件中創建。常規表空間文件可以在MySQL數據目錄內部或外部創建。有關更多信息,請參見官方文檔14.6.3.3 General Tablespaces。

ibdata文件

系統表空間文件,在 InnoDB系統表空間中創建的表在ibdata中創建。

3.5.1、系統表空間

系統表空間由一個或多個數據文件(ibdata文件)組成。其中包含與InnoDB相關對象有關的元數據(InnoDB 數據字典 data dictionary),以及更改緩衝區change buffer), 雙寫緩衝區doublewrite buffer)和撤消日誌undo logs)的存儲區 。

InnoDB 如果表是在系統表空間中創建的,則系統表空間中也包含表的表數據和索引數據。

系統表空間的問題

在MySQL 5.6.7之前,默認設置是將所有InnoDB表和索引保留 在系統表空間內,這通常會導致該文件變得非常大。因為系統表空間永遠不會縮小,所以如果先加載然後刪除大量臨時數據,則可能會出現存儲問題。

在MySQL 5.7中,默認設置為 獨立表空間模式,其中每個表及其相關索引存儲在單獨的 .ibd文件中。此默認設置使使用Barracuda文件格式的InnoDB功能更容易使用,例如表壓縮頁外列的有效存儲以及大索引鍵前綴(innodb_large_prefix)。

將所有表數據保留在系統表空間或單獨的 .ibd文件中通常會對存儲管理產生影響。

InnoDB在MySQL 5.7.6中引入了通用表空間[11],這些表空間也由.ibd文件表示 。通用表空間是使用CREATE TABLESPACE語法創建的共享表空間。它們可以在MySQL數據目錄之外創建,能夠容納多個表,並支持所有行格式的表。

3.5.2、獨立表空間

MySQL 5.7中,配置參數:innodb_file_per_table,默認處於啟用狀態,這是一個重要的配置選項,會影響InnoDB文件存儲,功能的可用性和I/O特性等。

啟用之後,每個表的數據和索引是存放在單獨的.ibd文件中的,而不是在系統表空間的共享ibdata文件中。

優點

  • 您可以更加靈活的選擇數據壓縮[12]的行格式,如:
    • 默認情況下(innodb_page_size=16K),前綴索引[13]最多包含768個字節。如果開啟innodb_large_prefix,且Innodb表的存儲行格式為 DYNAMIC 或 COMPRESSED,則前綴索引最多可包含3072個字節,前綴索引也同樣適用;
  • TRUNCATE TABLE執行的更快,並且回收的空間不會繼續保留,而是讓操作系統使用;
  • 可以在單獨的存儲設備上創建每表文件表空間數據文件,以進行I / O優化,空間管理或備份。請參見 14.6.1.2 Creating Tables Externally;

缺點

  • 獨立表空間中的未使用空間只能由同一個表使用,如果管理不當,會造成空間浪費;
  • 多個表需要刷盤,只能執行多次fsync,無法合併多個表的寫操作,這可能會導致更多的fsync操作總數;
  • mysqld必須為每個表文件空間保留一個打開的文件句柄,如果表數量多,可能會影響性能;
  • 每個表都需要自己的數據文件,需要更多的文件描述符;

即使啟用了innodb_file_per_table參數,每張表空間存放的只是數據、索引和插入緩存Bitmap頁,其他數據如回滾信息、插入緩衝索引頁、系統事務信息、二次寫緩衝等還是存放在原來的共享表空間中。

3.5.3、通用表空間

通用表空間使用CREATE TABLESPACE語法創建。

類似於系統表空間,通用表空間是共享表空間,可以存儲多個表的數據。

通用表空間比獨立表空間具有潛在的內存優勢,服務器在表空間的生存期內將表空間元數據保留在內存中。一個通用表空間通常可以存放多個表數據,消耗更少的表空間元數據內存。

數據文件可以放置在MySQL數據目錄或獨立於MySQL數據目錄。

3.5.4、undo表空間

undo表空間包含undo log。

innodb_rollback_segments變量定義分配給每個撤消表空間的回滾段的數量。

undo log可以存儲在一個或多個undo表空間中,而不是系統表空間中。

在默認配置中,撤消日誌位於系統表空間中。SSD存儲更適合undo log的I/O模式,為此,可以把undo log存放在有別於系統表空間的ssd硬盤中。

innodb_undo_tablespaces 配置選項控制undo表空間的數量。

3.5.5、臨時表空間

由用戶創建的非壓縮臨時表和磁盤內部臨時表是在共享臨時表空間中創建的。

innodb_temp_data_file_path 配置選項指定零時表空間文件的路徑,如果未指定,則默認在 innodb_data_home_dir目錄中創建一個略大於12MB 的自動擴展數據文件ibtmp1

使用ROW_FORMAT=COMPRESSED屬性創建的壓縮臨時表,是在獨立表空間中的臨時文件目錄中創建的 。

服務啟動的時候創建臨時表空間,關閉的時候銷毀臨時表空間。如果臨時表空間創建失敗,則意味着服務啟動失敗。

3.6、InnoDB底層邏輯存儲結構

在介紹索引之前,我們有必要了解一下InnoDB底層的邏輯存儲結構,因為索引是基於這個底層邏輯存儲結構創建的。截止到目前,我們所展示的都僅僅是物理磁盤中的邏輯視圖,接下來我們就來看看底層的視圖。

3.6.1、ibd文件組織結構

現在我們打開一個表空間ibd文件,看看裏面都是如何組織數據的?

如下圖,表空間由段(segment)、區(extent)、頁(page)組成。

InnoDB最小的存儲單位是頁,默認每個頁大小是16k。

而InnoDB存儲引擎是面向行的(row-oriented),數據按行進行存放,每個頁規定最多允許存放的行數=16k/2 – 200,即7992行。

段:如數據段、索引段、回滾段等。InnoDB存儲引擎是B+樹索引組織的,所以數據即索引,索引即數據。B+樹的恭弘=叶 恭弘子節點存儲的都是數據段的數據。

3.6.2、數據頁結構[14]

名稱 佔用空間 描述
Fil Header 38 byte 頁的基本信息,如所屬表空間,上一頁和下一頁指針。
Page Header 56 byte 數據頁專有的相關信息
Infimun + Supremum 26 byte 兩個虛擬的行記錄,用於限定記錄的邊界
User Records 動態分配 實際存儲的行記錄內容
Free Space 動態調整 尚未使用的頁空間
Page Directory 動態調整 頁中某些記錄的相對位置
Fil Trailer 8 byte 校驗頁是否完整

關於Infimun和Supremum:首次創建索引時,InnoDB會在根頁面中自動設置一個最小記錄和一個最高記錄,並且永遠不會刪除它們。最低記錄和最高記錄可以視為索引頁開銷的一部分。最初,它們都存在於根頁面上,但是隨着索引的增長,最低記錄將存在於第一或最低恭弘=叶 恭弘子頁上,最高記錄將出現在最後或最大關鍵字頁上。

3.6.3、行記錄結構描述[15]

先來講講Compact行記錄格式,Compact是MySQL5.0引入的,設計目標是高效的存儲數據,讓一個頁能夠存放更多的數據,從而實現更快的B+樹查找。

名稱 描述
變長字段長度列表 字段大小最多用2個字節表示,也就是最多限制長度:2^16=65535個字節;字段大小小於255字節,則用1個字節表示;
NULL標誌位 記錄該行哪些位置的字段是null值
記錄頭信息 記錄頭信息信息,固定佔用5個字節
列1數據 實際的列數據,NULL不佔用該部分的空間
列2數據

記錄頭用於將連續的記錄鏈接在一起,並用於行級鎖定。

每行數據除了用戶定義的列外,還有兩個隱藏列:

  • 6個字節的事務ID列;
  • 7個字節的回滾指針列;
  • 如果InnoDB沒有指定主鍵,還會增加一個6個字節的rowid列;

而記錄頭信息包[16]含如下內容:

名稱 大小(bit) 描述
() 1 未知
() 1 未知
deleted_flag 1 該行是否已被刪除
min_rec_flag 1 如果該記錄是預定義的最小記錄,則為1
n_owned 4 該記錄擁有的記錄數
heap_no 13 索引堆中該條記錄的排序號
record_type 3 記錄類型:000 普通,001 B+樹節點指針,010 Infimum,011 Supremum,1xx 保留
next_record 16 指向頁中下一條記錄

更詳細的頁結構參考官網:22.2 InnoDB Page Structure

更詳細的行結構參考官網:22.1 InnoDB Record Structure

更詳細的行格式參考官網:14.11 InnoDB Row Formats

根據以上格式,可以得出數據頁內的記錄組織方式:

3.6.3.1、MySQL中varchar最大長度是多少

上面表格描述我們知道,一個字段最長限制是65535個字節,這是存儲長度的限制。

而MySQL中對存儲是有限制的,具體參考:8.4.7 Limits on Table Column Count and Row Size

  • MySQL對每個表有4096列的硬限制,但是對於給定的表,有效最大值可能會更少;
  • MySQL表的每行行最大限製為65,535字節,這是邏輯的限制;實際存儲的時候,表的物理最大行大小略小於頁面的一半。如果一行的長度少於一頁的一半,則所有行都將存儲在本地頁面內。如果它超過一頁的一半,那麼將選擇可變長度列用於外部頁外存儲,直到該行大小控制在半頁之內為止。

而實際能夠存儲的字符是跟編碼有關的。

背景知識:

  • MySQL 4.0版本以下,varchar(10),代表10個字節,如果存放UTF8漢字,那麼只能存3個(每個漢字3字節);

  • MySQL 5.0版本以上,varchar(10),指的是10個字符,無論存放的是数字、字母還是UTF8漢字(每個漢字3字節),都可以存放10個,最大大小是65532字節

因此,Mysql5根據編碼不同,存儲大小也不同。

那麼假設我們使用的是utf8編碼,那麼每個字符最多佔用3個字節,也就是最多定義varchar(21845)個字符,如果是ascii編碼,一個字符相當於一個字節,最多定義varchar(65535)個字符,下面我們驗證下。

我們嘗試創建一個這樣的字段:

CREATE TABLE `t10` ( `id` int(11) NOT NULL,
                  `a` int(11) NOT NULL,
                  PRIMARY KEY (`id`)
                 ) ENGINE=InnoDB CHARSET=ascii ROW_FORMAT=Compact;


alter table t10 add `str` varchar(21845) DEFAULT NULL;

alter table t10 add `str` varchar(65535) DEFAULT NULL;

發現提示這個錯誤:

mysql> alter table t10 add `str` varchar(65535) DEFAULT NULL;
ERROR 1118 (42000): Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535. This includes storage overhead, check the manual. You have to change some columns to TEXT or BLOBs

原因是按照以上的行格式介紹,變長字段長度列表記錄也需要佔用空間,佔用2個字節,另外這裡是允許為空字段,在8位之內,所以NULL標誌位佔用1個字節,所以我們總共可以存儲的字符數是:

65535 – 2 – 2 – 4 – 4=65534

其中 -2 個字節表示變長字段列表,-1表示NULL標誌位,兩個-4表示兩個int類型字段佔用大小

所以實際上能夠容納的varchar大小為:65524,我們驗證下:

3.6.3.2、行記錄超過頁大小如何存儲

MySQL表的內部表示具有65,535字節的最大行大小限制。InnoDB 對於4KB,8KB,16KB和32KB innodb_page_size 設置,表的最大行大小(適用於本地存儲在數據庫頁面內的數據)略小於頁面的一半 。如果包含 可變長度列的InnoDB 行超過最大行大小,那麼將選擇可變長度列用於外部頁外存儲。

可變長度列由於太長而無法容納在B樹頁面上,這個時候會把可變長度列存儲在單獨分配的磁盤頁面上,這些頁面稱為溢出頁面,這些列稱為頁外列。頁外列的值存儲在由溢出頁面構成的單鏈接列表中。

InnoDB存儲引擎支持四種行格式:REDUNDANTCOMPACTDYNAMIC,和COMPRESSED。不同的行格式,對溢出的閾值和處理方式有所區別,詳細參考:14.11 InnoDB Row Formats。

COMPACT行格式處理方式

使用COMPACT行格式的表將前768個字節的變長列值(VARCHARVARBINARYBLOBTEXT類型)存儲在B樹節點內的索引記錄中,其餘的存儲在溢出頁上。

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

台中搬家公司推薦超過30年經驗,首選台中大展搬家

如果列的值等於或小於768個字節,則不使用溢出頁,因此可以節省一些I / O。

如果查過了768個字節,那麼會按照如下方式進行存儲:

DYNAMIC行格式處理方式

DYNAMIC行格式提供與COMPACT行格式相同的存儲特性,但改進了超長可變長度列的存儲能力和支持大索引鍵前綴。

InnoDB 可以完全在頁外存儲過長的可變長度列值(針對 VARCHARVARBINARYBLOBTEXT類型),而聚集索引記錄僅包含指向溢出頁的20字節指針。大於或等於768字節的固定長度字段被編碼為可變長度字段。

表中大字段引發的問題

如果一個表中有過多的可變長度大字段,導致一行記錄太長,而整個時候使用的是COMPACT行格式,那麼就可能會插入數據報錯。

如,頁面大小事16k,根據前面描述我們知道,MySQL限制一頁最少要存儲兩行數據,如果很多可變長度大字段,在使用COMPACT的情況下,仍然會把大字段的前面768個字節存在索引頁中,可以算出最多支持的大字段:1024 * 16 / 2 / 768 = 10.67,那麼超過10個可變長度大字段就會插入失敗了。

這個時候可以把row format改為:DYNAMIC。

3.7、索引

前面我們了解了InnoDB底層的存儲結構,即:以B+樹的方式組織數據頁。另外了解了數據頁中的數據行的存儲方式。

而構建B+樹索引的時候必須要選定一個或者多個字段作為索引的值,如果索引選擇的是主鍵,那麼我們就稱為聚集索引,否則就是二級索引。

為什麼MySQL使用B+樹?

  • 哈希表雖然可以提供O(1)的單行數據操作性能,但卻不能很好的支持排序和範圍查找,會導致全表掃描;
  • B樹可以再非恭弘=叶 恭弘子節點存儲數據,但是這可能會導致查詢連續數據的時候增加更多的I/O操作;
  • 而B+樹數據都存放在恭弘=叶 恭弘子節點,恭弘=叶 恭弘子節點通過指針相互連接,可以減少順序遍歷時產生的額外隨機I/O

更新詳細解釋: 為什麼 MySQL 使用 B+ 樹[17]

3.7.1、聚集索引

了解到上面的底層邏輯存儲結構之後,我們進一步來看看InnoDB是怎麼通過B+樹來組織存儲數據的。

首先來介紹下聚集索引。

聚集索引

主鍵索引的InnoDB術語。

下面我們創建一張測試表,並插入數據,來構造一顆B+樹:

CREATE TABLE t20 (
id int NOT NULL,
a int NOT NULL,
b int,
c int,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;

insert into t20 values(20, 1, 2, 1);
insert into t20 values(40, 1, 2, 5);
insert into t20 values(30, 3, 2, 4);
insert into t20 values(50, 3, 6, 2);
insert into t20 values(10, 1, 1, 1);

可以看到,雖然我們是id亂序插入的,但是插入之後查出來的確是排序好的:

這個排序就是B+索引樹構建的。

我們可以通過這個在線的動態演示工具來看看B+樹的構造過程,最終結果如下:

實際存放在數據庫中的模型因頁面大小不一樣而有所不同,這裏為了簡化模型,我們按照B+樹的通用模型來解釋數據的存儲結構。

類似的,我們的數據也是這種組織形式的,該B+樹中,我們以主鍵為索引進行構建,並且把完整的記錄存到對應的頁下面:

其中藍色的是索引頁,橙色的是數據頁。

每個頁的大小默認為16k,如果插入新的數據行,這個時候就要申請新的數據頁了,然後挪動部分數據過去,重新調整B+樹,這個過程稱為頁分裂,這個過程會影響性能。

相反的,如果InnoDB索引頁的填充因子下降到之下MERGE_THRESHOLD,默認情況下為50%(如果未指定),則InnoDB嘗試收縮索引樹以釋放頁面。

自增主鍵的插入是遞增順序插入的,每次添加記錄都是追加的,不涉及到記錄的挪動,不會觸發恭弘=叶 恭弘子節點的分裂,而一般業務字段做主鍵,往往都不是有序插入的,寫成本比較高,所以我們更傾向於使用自增字段作為主鍵。

聚集索引注意事項

  • 當在表上面定義了PRIMARY KEY之後,InnoDB會把它作為聚集索引。為此,為你的每個表定義一個PRIMARY KEY。如果沒有唯一併且非空的字段或者一組列,那麼請添加一個自增列;
  • 如果您沒有為表定義PRIMARY KEY,則MySQL會找到第一個不帶null值的UNIQUE索引,並其用作聚集索引;
  • 如果表沒有PRIMARY KEY或沒有合適的UNIQUE索引,則InnoDB 內部會生成一個隱藏的聚集索引GEN_CLUST_INDEX,作為行ID,行ID是一個6字節的字段,隨着數據的插入而自增。

聚集索引查找

根據索引進行查找id=50的記錄,如下圖,沿着B+樹一直往下尋找,最終找到第四頁,然後把該頁加載到buffer pool中,在緩存中遍歷對比查找,由於裏面的行記錄是順序組織的,所以很快就可以定位到記錄了。

3.7.2、輔助索引

除了聚集索引之外的所有索引都稱為輔助索引(二級索引)。在InnoDB中,輔助索引中每個記錄都包含該行的主鍵列以及為輔助索引指定的列。

在輔助索引中查找到記錄,可以得到記錄的主鍵索引ID,然後可以通過這個主鍵索引ID去聚集索引中搜索具體的記錄,這個過程稱為回表操作。

如果主鍵較長,則輔助索引將使用更多空間,因此具有短的主鍵是有利的。

下面我們給剛剛的表添加一個組合聯合索引

-- 添加多一個字段
alter table t20 add column d varchar(20) not null default '';
-- 添加一個聯合索引
alter table t20 add index idx_abc(a, b, c);

添加之後組合索引B+樹如下,其中索引key為abc三個字段的組合,索引存儲的記錄為主鍵ID:

覆蓋索引(Using index)

InnoDB存儲引擎支持覆蓋索引,即從輔助索引中就可以得到查詢的記錄,而不需要回表去查詢聚集索引中的記錄,從而減少大量的IO操作。下面的查詢既是用到了覆蓋索引 idx_abc:

select a, b from t20 where a > 2;

執行結果如下:

可以發現,Extra這一列提示Using index,使用到了覆蓋索引,掃描的行數為2。注意:這裏的掃描行數指的是MySQL執行器從引擎取到兩條記錄,引擎內部可能會遍歷到多條記錄進行條件比較。

最左匹配原則

由於InnoDB索引式B+樹構建的,因此可以利用索引的“最左前綴”來定位記錄。

也就是說,不僅僅是用到索引的全部定義字段會走索引,只要滿足最左前綴,就可以利用索引來加速檢索。這個最左前綴可以是聯合索引的最左n個字段。

索引條件下推(Using index condition)

索引條件下推 Index Condition Pushdown (ICP),是針對MySQL使用索引從表中檢索行的情況的一種優化。

為什麼叫下推呢,就是在滿足要求的情況下,把索引的條件丟給存儲引擎去判斷,而不是把完整的記錄傳回MySQL Server層去判斷。

ICP支持range, ref, eq_ref, 和 ref_or_null類型的查找,支持MyISAM和InnoDB存儲引擎。

不能將引用子查詢的條件下推,觸發條件不能下推。詳細規則參考:Index Condition Pushdown

如果不使用ICP,則存儲引擎將遍歷索引以在聚集索引中定位行,並將結果返回給MySQL Server層,MySQL Server層繼續根據WHERE條件進行篩選行。

啟用ICP后,如果WHERE可以僅使用索引中的列來評估部分條件,則MySQL Server層會將這部分條件壓入WHERE條件下降到存儲引擎。然後,存儲引擎通過使用索引條目來判斷索引條件,在滿足條件的情況下,才回表去查找記錄返回給MySQL Server層。

ICP的目標是減少回表掃描的行數,從而減少I / O操作。對於InnoDB表,ICP僅用於二級索引。

使用索引下推的時候,執行計劃中的Extra會提示:Using index condition,而不是Using index,因為必須回表查詢整行數據。Using index代表使用到了覆蓋索引。

3.8、InnoDB Data Directory

InnoDB數據字典(Data Directory)存放於系統表空間中,主要包含元數據,用於追蹤表、索引、表字段等信息。由於歷史的原因,InnoDB數據字典中的元數據與.frm文件中的元數據重複了。

3.9、Doublewrite Buffer

雙寫緩衝區(Doublewrite Buffer)是一個存儲區,是InnoDB在tablespace上的128個頁(2個區),大小是2MB[18]

版本區別:在MySQL 8.0.20之前,doublewrite緩衝區存儲區位於InnoDB系統表空間中。從MySQL 8.0.20開始,doublewrite緩衝區存儲區位於doublewrite文件中。

本文基於MySQL 5.7編寫。

操作系統寫文件是以4KB為單位的,那麼每寫一個InnoDB的page到磁盤上,操作系統需要寫4個塊。如果寫入4個塊的過程中出現系統崩潰,那麼會導致16K的數據只有一部分寫是成功的,這種情況下就是partial page write(部分頁寫入)問題。

InnoDB這個時候是沒法通過redo log來恢復的,因為這個時候頁面的Fil Trailer(Fil Trailer 主要存放FIL_PAGE_END_LSN,主要包含頁面校驗和以及最後的事務)中的數據是有問題的。

為此,每當InnoDB將頁面寫入到數據文件中的適當位置之前,都會首先將其寫入雙寫緩衝區。只有將緩衝區安全地刷新到磁盤后,InnoDB才會將頁面寫入最終的數據文件。

如果在頁面寫入過程中發生操作系統或者mysqld進程崩潰,則InnoDB可以在崩潰恢復期間從雙寫緩衝區中找到頁面的完好副本用於恢復。恢復時,InnoDB掃描雙寫緩衝區,併為緩衝區中的每個有效頁面檢查數據文件中的頁面是否完整。

如果系統表空間文件(“ ibdata文件 ”)位於支持原子寫的Fusion-io設備上,則自動禁用雙寫緩衝,並且將Fusion-io原子寫用於所有數據文件。

3.10、Redo Log

重做日誌(Redo Log)主要適用於數據庫的崩潰恢復,用於實現數據的完整性。

重做日誌由兩部分組成:

  • 重做日誌緩衝區 Log Buffer;
  • 重做日誌文件,重做日誌文件在磁盤上由兩個名為ib_logfile0ib_logfile1的物理文件表示。

為了實現數據完整性,在臟頁刷新到磁盤之前,必須先把重做日誌寫入到磁盤。除了數據頁,聚集索引、輔助索引以及Undo Log都需要記錄重做日誌。

3.10.1、Redo Log在事務中的寫入時機

在事務中,除了寫Redo log,還需要寫binlog,為此,我們先來簡單介紹下binlog。

3.10.1.1、binlog

全寫:Binary Log,二進制log。二進制日誌是一組日誌文件。其中包含有關對MySQL服務器實例進行的數據修改的信息。

Redo Log是InnoDB引擎特有的,而binlog是MySQL的Server層實現的,所有引擎都可以使用。

Redo Log的文件是循環寫的,空間會用完,binlog日誌是追加寫的,不會覆蓋以前的日誌。

binlog主要的目的:

  • 主從同步,主服務器將二進制日誌中包含的事件發送到從服務器,從服務器執行這些事件,以保持和主服務器相同的數據更改;
  • 某些數據恢復操作需要使用二進制日誌,還原到某一個備份點。

binlog主要是用於主從同步和數據恢復,Redo Log主要是用於實現事務數據的完整性,讓InnoDB具有不會丟失數據的能力,又稱為crash-safe。

binlog日誌的兩種記錄形式:

  • 基於SQL的日誌記錄:事件包含產生數據更改(插入,新增,刪除)的SQL語句;
  • 基於行的日誌記錄:時間描述對單個行的更改。

混合日誌記錄默認情況下使用基於語句的日誌記錄,但根據需要自動切換到基於行的日誌記錄。

3.10.1.2、Redo Log在事務中的寫入時機

簡單的介紹完binlog,我們再來看看Redo Log的寫入流程。

假設我們這裏執行一條sql

update t20 set a=10 where id=1;

執行流程如下:

3.10.2、如何保證數據不丟失

前面我們介紹Log Buffer的時候,提到過,為了保證數據不丟失,我們需要執行以下操作:

  • 如果啟用了binlog,則設置:sync_binlog=1;
  • innodb_flush_log_at_trx_commit=1;
  • sync_binlog=0:表示每次提交事務都只 write,不 fsync;
  • sync_binlog=1:表示每次提交事務都會執行 fsync;
  • sync_binlog=N(N>1) :表示每次提交事務都 write,但累積 N 個事務后才 fsync。

這兩個的作用相當於在上面的流程最後一步,提交事務接口返回Server層之前,把binlog cache和log buffer都fsync到磁盤中了,這樣就保證了數據的落盤,不會丟失,即使奔潰了,也可以通過binlog和redo log恢複數據相關流程如下:

在磁盤和內存中的處理流程如下面編號所示:

其中第四步log buffer持久化到磁盤的時機為:

  • log buffer佔用的空間即將達到innodb_log_buffer_size一半的時候,後台線程主動寫盤;
  • InnoDB後台有個線程,每隔1秒會把log buffer刷到磁盤;
  • 由於log buffer是所有線程共享的,當其他事務線程提交時也會導致已寫入log buffer但還未提交的事務的redo log一起刷新到磁盤

其中第五步:臟頁刷新到磁盤的時機為:

  • 系統內存不足,需要淘汰臟頁的時候,要把臟頁同步回磁盤;
  • MySQL空閑的時候;
  • MySQL正常關閉的時候,會把臟頁flush到磁盤。

參數innodb_max_dirty_pages_pct是臟頁比例上限,默認值是 75%。

為什麼第二步 redo log prepare狀態也要寫磁盤?

因為這裏先寫了,才能確保在把binlog寫到磁盤后崩潰,能夠恢複數據:如果判斷到redo log是prepare狀態,那麼查看是否存XID對應的binlog,如果存在,則表示事務成功提交,需要用prepare狀態的redo log進行恢復。

這樣即使崩潰了,也可以通過redo log來進行恢復了,恢複流程如下:

Redo Log是循環寫的,如下圖:

  • writepos記錄了當前寫的位置,一邊寫位置一邊往前推進,當writepos與checkpoint重疊的時候就表示logfile寫滿了,綠色部分表示是空閑的空間,紅色部分是寫了redo log的空間;
  • checkpoint處標識了當前的LSN,每當系統崩潰重啟,都會從當前checkpoint這個位置執行重做日誌,根據重做日誌逐個確認數據頁是否沒問題,有問題就通過redo log進行修復。

LSN Log Sequence Number的縮寫。代表日誌序列號。在InnoDB中,LSN佔用8個字節,單調遞增,LSN的含義:

  • 重做日誌寫入的總量;
  • checkpoint的位置;
  • 頁的版本;

除了重做日誌中有LSN,每個頁的頭部也是有存儲了該頁的LSN,我們前面介紹頁面格式的時候有介紹過。

在頁中LSN表示該頁最後刷新時LSN的大小。[19]

3.11、Undo Logs

上面說的redo log記錄了事務的行為,可以通過其對頁進行重做操作,但是食物有時候需要進行回滾,這時候就需要undo log了[20]

關於Undo Log的存儲:InnoDB中有回滾段(rollback segment),每個回滾段記錄1024個undo log segment,在每個undo log segment段中進行申請undo頁。系統表空間偏移量為5的頁記錄了所有的rollback segment header所在的頁。

3.11.1、undo log的格式

根據行為不同分為兩種:

insert undo log

insert undo log:只對事務本身可見,所以insert undo log在事務提交后可直接刪除,無需執行purge操作;

insert undo log主要記錄了:

next 記錄下一個undo log的位置
type_cmpl undo的類型:insert or update
*undo_no 記錄事務的ID
*table_id 記錄表對象
*len1, col1 記錄列和值
*len2, col2 記錄列和值
start 記錄undo log的開始位置

假設在事務1001中,執行以下sql,t20的table_id為10:

insert into t20(id, a, b, c, d) values(12, 2, 3, 1, "init")

那麼對應會生成一條undo log:

update undo log

update undo log:執行update或者delete會產生undo log,會影響已存在的記錄,為了實現MVCC(後邊介紹),update undo log不能再事務提交時立刻刪除,需要將事務提交時放入到history list上,等待purge線程進行最後的刪除操作。

update undo log主要記錄了:

next 記錄下一個undo log的位置
type_cmpl undo的類型:insert or update
*undo_no undo日誌編號
*table_id 記錄表對象
info_bits
*DATA_TRX_ID 事務的ID
*DATA_ROLL_PTR 回滾指針
*len1, i_col1 n_unique_index
*len2, i_col2
n_update_fields 以下是update vector信息,表示update操作導致發送改變的列
*pos1, *len1, u_old_col1
*pos2, *len2, u_old_col2
n_bytes_below
*pos, *len, col1
*pos, *len, col2
start 記錄undo log的開始位置

假設在事務1002中,執行以下sql,t20的table_id為10:

update t20 set d="update1" where id=60;

那麼對應會生成一條undo log:

如上圖,每回退應用一個undo log,就回退一個版本,這就是MVCC(Multi versioning concurrency control)的實現原理。

下面我們在執行一個delete sql:

delete from t20 where id=60;

對應的undo log變為如下:

如上圖,實際的行記錄不會立刻刪除,而是在行記錄頭信息記錄了一個deleted_flag標誌位。最終會在purge線程purge undo log的時候進行實際的刪除操作,這個時候undo log也會清理掉。

3.11.2、MVCC實現原理

如上圖所示,MySQL只會有一個行記錄,但是會把每次執行的sql導致行記錄的變動,通過undo log的形式記錄起來,undo log通過回滾指針連接在一起,這樣我們想回溯某一個版本的時候,就可以應用undo log,回到對應的版本視圖了。

我們知道InnoDB是支持RC(Read Commit)和RR(Repeatable Read)事務隔離級別的,而這個是通過一致性視圖(consistent read view)實現的。

一個事務開啟瞬間,所有活躍的事務(未提交)構成了一個視圖數組,InnoDB就是通過這個視圖數組來判斷行數據是否需要undo到指定的版本:

RR事務隔離級別

假設我們使用了RR事務隔離級別。我們看個例子:

如下圖,假設id=60的記錄a=1

事務C啟動的瞬間,活躍的事務如下圖黃色部分所示:

也就是對於事務A、事務B、事務C,他們能夠看到的數據只有是行記錄中的最大事務IDDATA_TRX_ID<=11的,如果大於,那麼只能通過undo進行回滾了。如果TRX_ID=當前事務id,也可以看到,即看到自己的改動。

另外有一個需要注意的:

  • 在RR隔離級別下,當事務更新事務的時候,只能用當前讀來獲取最新的版本數據來更新,如果當前記錄的行鎖被其他事務佔用,就需要進入所等待;
  • 在RC隔離級別下,每個語句執行都會計算出新的一致性視圖。

所以我們分析上面的例子的執行流程:

  • 事務C執行update,執行當前讀,拿到的a=1,然後+1,最終a=2,同時添加一個TRX_ID=11的undo log;
  • 事務B執行select,使用快照讀,記錄的DATA_TRX_ID > 11,所以需要通過undo log回滾到DATA_TRX_ID=11的版本,所以拿到的a是1;
  • 事務B執行update,需要使用當前讀,拿到最新的記錄,a=2,然後加1,最終a=3;
  • 事務B執行select,拿到當前最新的版本,為自己的事務id,所以得到a=3;
  • 事務A執行select,使用快照讀,記錄的DATA_TRX_ID > 11,所以需要通過undo log回滾到DATA_TRX_ID=11的版本,所以拿到的a是1。
  • 如果是RC隔離級別,執行select的時候會計算出新的視圖,新的視圖能夠看到的最大事務ID=14,由於事務B還沒提交,事務C提交了,所以可以得到a=2:

總結

  • 數據完整性依靠:redo log
  • 事務隔離級別的實現依靠MVCC,MVCC依靠undo log實現
  • IO性能提升方式:buffer pool加快查詢效率和普通索引更新的效率,log buffer對日誌寫的性能提升
  • 查詢性能提升依賴於索引,底層用頁存儲,字段越小頁存儲越多行記錄,查詢效率越快;自增字段作為聚集索引可以加快插入操作;
  • 故障恢復:雙寫緩衝區、redo log
  • 主從同步:binlog

本文內容比較多,看完之後需要多梳理,最後大家可以對照着這個思維導圖回憶一下,這些內容是否都記住了:

這篇文章的內容就差不多介紹到這裏了,能夠閱讀到這裏的朋友真的是很有耐心,為你點個贊。

本文為arthinking基於相關技術資料和官方文檔撰寫而成,確保內容的準確性,如果你發現了有何錯漏之處,煩請高抬貴手幫忙指正,萬分感激。

大家可以關注我的博客:itzhai.com 獲取更多文章,我將持續更新後端相關技術,涉及JVM、Java基礎、架構設計、網絡編程、數據結構、數據庫、算法、併發編程、分佈式系統等相關內容。

如果您覺得讀完本文有所收穫的話,可以關注我的賬號,或者點贊吧,碼字不易,您的支持就是我寫作的最大動力,再次感謝!

關注我的公眾號,及時獲取最新的文章。

更多文章

  • JVM系列專題:公眾號發送 JVM

本文作者: arthinking

博客鏈接: https://www.itzhai.com/database/insight-into-the-underlying-architecture-of-mysql-buffer-and-disk.html

洞悉MySQL底層架構:遊走在緩衝與磁盤之間

版權聲明: BY-NC-SA許可協議:創作不易,如需轉載,請聯繫作者,謝謝!

References

  1. innodb_data_home_dir. Retrieved from https://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_data_home_dir ↩︎

  2. ib_buffer_pool. Retrieved from https://dev.mysql.com/doc/refman/5.6/en/innodb-preload-buffer-pool.html ↩︎

  3. ib_logfile0. Retrieved from https://dev.mysql.com/doc/refman/5.7/en/innodb-redo-log.html ↩︎

  4. ibtmp1. Retrieved from https://dev.mysql.com/doc/refman/5.7/en/innodb-temporary-tablespace.html ↩︎

  5. db.opt. Retrieved from https://dev.mysql.com/doc/refman/8.0/en/data-dictionary-file-removal.html ↩︎

  6. Linux Programmer’s Manual – OPEN(2). (2020-02-09). Retrieved from http://man7.org/linux/man-pages/man2/open.2.html ↩︎

  7. man-pages.write. (2019-10-10). Retrieved from http://man7.org/linux/man-pages/man2/write.2.html ↩︎

  8. man-pages.fdatasync. (2019-03-06). Retrieved from http://man7.org/linux/man-pages/man2/fdatasync.2.html ↩︎

  9. On Disk IO, Part 1: Flavors of IO. medium.com. Retrieved from https://medium.com/databasss/on-disk-io-part-1-flavours-of-io-8e1ace1de017 ↩︎

  10. Innodb calls fsync for writes with innodb_flush_method=O_DIRECT. Retrieved from https://bugs.mysql.com/bug.php?id=45892 ↩︎

  11. 14.6.3.3 General Tablespaces. Retrieved from https://dev.mysql.com/doc/refman/5.7/en/general-tablespaces.html ↩︎

  12. MYSQL INNODB表壓縮. (2018-03-09). Retrieved from https://cloud.tencent.com/developer/article/1056453 ↩︎

  13. 前綴索引,一種優化索引大小的解決方案. (2015-03-03). Retrieved from https://www.cnblogs.com/studyzy/p/4310653.html ↩︎

  14. MySQL Internals Manual – innodb page structure[EB/OL]. (2020-05-04). Retrieved 2020-0530, from https://dev.mysql.com/doc/internals/en/innodb-page-structure.html ↩︎

  15. official.MySQL Internals Manual – innodb record structure[EB/OL]. (2020-05-04). Retrieved 2020-0530, from https://dev.mysql.com/doc/internals/en/innodb-record-structure.html ↩︎

  16. 姜承堯. MySQL技術內幕-InnoDB存儲引擎第二版[M]. 机械工業出版社, 2013-5:104. ↩︎

  17. 為什麼 MySQL 使用 B+ 樹. draveness.me. (2019-12-11). Retrieved from https://draveness.me/whys-the-design-mysql-b-plus-tree/ ↩︎

  18. InnoDB DoubleWrite Buffer as Read Cache using SSDs∗. Retrieved from https://www.usenix.org/legacy/events/fast12/poster_descriptions/Kangdescription2-12-12.pdf ↩︎

  19. 姜承堯. MySQL技術內幕-InnoDB存儲引擎第二版[M]. 机械工業出版社, 2013-5:302-303. ↩︎

  20. 姜承堯. MySQL技術內幕-InnoDB存儲引擎第二版[M]. 机械工業出版社, 2013-5:306. ↩︎

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

台中搬家公司費用怎麼算?

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

買誰都不吃虧 博越和RX5該如何選?_如何寫文案

※別再煩惱如何寫文案,掌握八大原則!

什麼是銷售文案服務?A就是幫你撰寫適合的廣告文案。當您需要販售商品、宣傳活動、建立個人品牌,撰寫廣告文案都是必須的工作。

0L兩款發動機,而且主力車型就是1。8T的動力裝配。吉利博越的1。8T發動機賬面參數是184ps馬力,最大扭矩285N。m,對比RX5 1。5T 169ps馬力和250N。m的峰值扭矩還是強了不少。配置博越的配置更加豐富1。8TD自動智慧型的吉利博越和 20T兩驅自動旗艦版的榮威RX5指導價格差價僅僅只是在1000元,而且兩車的配置都算不錯,操控配置更是完全一樣。

當下的汽車增長速度最快的車型無疑是SUV,無論是合資企業還是自主品牌車企對於SUV車型的研發力度也是非常巨大,同樣作為自主品牌,吉利博越和榮威RX5各自代表了目前各自品牌的最高技術,這兩款售價區間重疊明顯,同為國貨精品的SUV如何選擇?

我們拿出這兩台車售價最接近的細分配置車型進行對比;售價也符合當下眾多購車人士的預算訴求,十五萬以內:

吉利博越2016款1.8TD 自動智慧型,售價13.98萬

榮威RX5 2016款 20T 兩驅自動旗艦版,售價13.88萬

外觀

爭議頗大VS迎合主流

吉利博越到底好不好看?不同的人有不同的答案;吉利博越的外觀一直存在着爭議,主要的焦點集中於那採用自沃爾沃概念車的漣漪式前臉格柵。但是從辨識度上說,吉利博越是成功的,漣漪式前臉已經成為了吉利的家族標誌,精緻的設計風格已經為吉利樹立起了一種不錯的品牌形象。

榮威RX5的設計則是一種非常迎合當下審美潮流的風格,前臉進氣格柵線條流暢舒展,配合上矩陣式的燈組設計,層次豐富之下更體現出不少的細節處理。榮威RX5的車身運用了大量的黃金比例理念,將整車的視覺效果做到了很好的均衡,看過實車的人都會說,RX5是一款看上去很“大氣”的SUV。

內飾

各有千秋;博越設計感更豐富

在內飾層面小編個人是比較喜歡博越的內飾設計,運用了更多的柔和曲線設計,官方稱為西湖斷橋的元素,從使用上說,博越的內飾沒有為了體現設計感而喪失人機工程的實用性。功能性按鍵布局顯得清晰明確,操作起來也十分方便。

反觀榮威RX5,

※教你寫出一流的銷售文案?

銷售文案是什麼?A文案是廣告用的文字。舉凡任何宣傳、行銷、販賣商品時所用到的文字都是文案。在網路時代,文案成為行銷中最重要的宣傳方式,好的文案可節省大量宣傳資源,達成行銷目的。

第一眼看到榮威RX5的內飾我會想到大眾,整體設計以平實簡潔的路線為主,儘管在設計感上或許相比較於博越有一定的欠缺,但是在功能性空間表現性上也一樣有着很優秀的表現。

空間

榮威RX5空間更充裕

儘管兩台車的定位都是緊湊型SUV,但是榮威RX5的車身尺寸要比博越大了半圈,作為一款有着2700軸距的SUV車型,兩車從乘坐空間表現性上說榮威RX5要更勝一籌。

動力

博越的動力更加澎湃

榮威RX5搭載的是兩套動力總成,分別是1.5T和2.0T的渦輪增壓發動機,而吉利博越同樣也是兩套動力總成,但是1.8T以及2.0L兩款發動機,而且主力車型就是1.8T的動力裝配。

吉利博越的1.8T發動機賬面參數是184ps馬力,最大扭矩285N.m,對比RX5 1.5T 169ps馬力和250N.m的峰值扭矩還是強了不少。

配置

博越的配置更加豐富

1.8TD自動智慧型的吉利博越和 20T兩驅自動旗艦版的榮威RX5指導價格差價僅僅只是在1000元,而且兩車的配置都算不錯,操控配置更是完全一樣。

但是總體上來說博越的配置更加豐富,主要的是體現在安全性配置方面,博越搭載的前排側安全氣囊和前後頭部氣囊在名為旗艦版的榮威RX5身上並沒有裝配。

而在燈光配置方面,榮威RX5的配置則顯得有些寒酸了,原廠搭載的是鹵素光源,可以選裝LED,並且沒有霧燈標配,而這一點,價位幾乎相同的博越則有了更完善的裝備。

編輯總結:兩款車對比下來,更建議購買的還是吉利博越,榮威RX5更多的是以2.0T的車型作為主力車型向合資緊湊型SUV進行競爭,而1.5T的動力車型所搭載的配置則明顯的與2.0T車型拉開了差距。

榮威RX5的2.0T車型售價在16.68-18.68萬,從定價來說這個價格並不算低,儘管以互聯網為背景的科技性配置的確是一大亮點,但是更多的消費者更注重的是車內所能給予的實用性配置享受,而兩車相比,13.98萬的吉利博越1.8TD自動智慧型則更具有性價比。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。

20萬預算買進口SUV 動力和個性化這三台都可以_網頁設計公司

※想知道最厲害的網頁設計公司嚨底家"!

RWD(響應式網頁設計)是透過瀏覽器的解析度來判斷要給使用者看到的樣貌

一些細節方面,卡繽還是做得比較有意思的。副駕駛位的抽屜箱是橫拉打開,而非傳統的圓弧形打開,這樣駕駛員就可以在打開抽屜時,清楚看到裏面的東西。前排座椅後方用了五條限位繩來固定物品,而非傳統的網兜,這點我就覺得不慎實用。

斯巴魯XV

指導價:18.98—22.98萬

編者點評:

XV可能是這個價位區間中我們比較常見的進口SUV,以這個價格就能享受到水平對置發動機與全時四驅所帶來的快感,確實不賴。然而只有2.0L自然吸氣發動機+CVT變速箱,動力上難言充足,也僅僅是夠用而已。儘管跑得不夠快,但是有全時四驅的加持,操控性那是沒得說。

進入車廂內,XV給人的感覺比較嚴肅,到處都是黑色和銀色的內飾。不過,這也有一個好處,那就是比較耐臟。雖然整個內飾略簡單,但是從那些接線處細細一看還是能發現XV的做工有一種精緻范。

保養方面,按照4S店的建議,6萬公里下來的保養費用為10594元。這個價格就算是比較貴了,不過,想想要維護一台水平對置發動機,這又似乎變得不是那麼難以接受。

雷諾 卡繽

指導價:13.98—18.88萬

編者點評:

卡繽這台車可能很少有人聽說過,不過它可曾是歐洲小型SUV的銷量冠軍。能做冠軍肯定是有不少真材實料的。先從外形說起,卡繽採用了時下流行的雙色車身,而且都是明亮的色彩,車尾還可以選擇一些拉花,整台車看起來就是各種酷炫,相當討年輕人喜歡的風格。

動力總成方面,卡繽搭載的是1.2T渦輪增壓發動機+6速雙離合。1.2T的發動機最大功率為85KW,最大扭矩為190Nm。動力相對pSA那台1.2T發動機來說,是“書生”了一點點,不過卡繽也不是一台性能取向的車。

一些細節方面,卡繽還是做得比較有意思的。副駕駛位的抽屜箱是橫拉打開,而非傳統的圓弧形打開,

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

透過資料庫的網站架設建置,建立公司的形象或購物系統,並提供最人性化的使用介面,讓使用者能即時接收到相關的資訊

這樣駕駛員就可以在打開抽屜時,清楚看到裏面的東西。前排座椅後方用了五條限位繩來固定物品,而非傳統的網兜,這點我就覺得不慎實用。尾箱隔板有兩面,一面是常規的絨面,可以用來放置一些摩擦力較小的物品,防止滑動;另一面是光面,可以用來放一些比較容易弄髒的物品,這樣也便於清潔。在這點上,法國人還是想得比較細心。

保養方面,6萬公里中的保養費用為5926元。這樣的養護成本在同級別中,算是很便宜了。雷諾還給出了一個殺手鐧,那就是送車主10次免費更換機油和機油濾清器,這樣的待遇恐怕也沒哪個廠家敢給。

鈴木 吉姆尼

指導價:14.18—16.08萬

編者點評:

吉姆尼這台車是很多越野愛好者心中的完美伴侶,車身長度僅為3665mm,這樣短小精悍的車身造型可以很好地通過不少比較窄的路面,在城市中穿越也比較輕鬆,特別是在找停車位的時候,許多地方的停車位規劃並不完善,而且尺寸偏小。這時候,吉姆尼甚至比普通轎車還更容易泊進去。

不過,吉姆尼的安全配置就十分寒酸了,僅僅有ABS防抱死,連ESp都沒有。其他配置方面,也同樣乏善可陳。買回來要好好越野肯定是要先去改裝一下的,起碼給它裝兩把前橋和後橋的差速鎖。

吉姆尼的內飾可以說是樸實無華,一眼望去儘是各種塑料。話雖如此,但是它的塑料還是做得比較用心的。油耗方面,手動版的百公里平均油耗為7.7L,而自動版的則為9.3L。相差還是挺大的。可以看出,儘管吉姆尼的排量較低,但也不是一個省油的主。

除非你是一個越野愛好者,不然這台車在城市中也許只能帶給你較高的回頭率。

這三款車各有各的特點,XV算是進口車中比較常見的,卡繽則充滿陽光,相當有范,而吉姆尼顯然就屬於劍走偏鋒的類型。選擇一台進口車,同時也是選擇了另一種生活。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

當全世界的人們隨著網路時代而改變向上時您還停留在『網站美醜不重要』的舊有思維嗎?機會是留給努力改變現況的人們,別再浪費一分一秒可以接觸商機的寶貴時間!