面試 Q&A (二)

Jinx19發表於2018-08-09

java中的基本資料型別

參考:

基本資料型別 位元組 範圍 預設值
byte 8-bit 1個位元組 -128 ~ 127 0
short 16-bit 2個位元組 -32768 ~ 32767 0
int 32-bit 4個位元組 -2^31 ~ 2^31 - 1 0
long 64-bit 8個位元組 -2^63 ~ 2^63 -1 0L
float 32-bit 4個位元組 0.0f
double 64-bit 8個位元組 0.0d
boolean 1-bit false
char 16-bit unicode字元 '\u0000'~'\uffff' '\u0000'

char = '中文'可以通過編譯麼?

可以。

public class primitivetype {
  public static void main(String[] args) {
    char i = '中';
    System.out.println(i);
  }
}
複製程式碼

可以執行輸出'中'

Java中的一個char採用的是Unicode編碼集,佔用兩個位元組,而一箇中文字元也是兩個位元組,因此Java中的char是可以表示一箇中文字元的。

但是在C/C++中由於採用的字元編碼集是ASCII,只有一個位元組,因此是沒辦法表示一箇中文字元的。

Java中的char是否可以儲存一箇中文字元之理解字元位元組以及編碼集

char幾個位元組?儲存中文的char是幾個位元組?

java中char型別固定佔2個位元組。(注:char型別也可以儲存一個漢字)。

以utf8為例,utf8是一個變長編碼標準,可以以1~4個位元組表示一個字元,而中文佔3個位元組,ascII字元佔1個位元組。

那麼為什麼我們在java裡面可以用一個char來表示一箇中文呢?

因為java是以unicode作為編碼方式的。unicode是一個定長的編碼標準,每個字元都是2個位元組,也就是1個char型別的空間。

在編譯時會把utf8的中文字元轉換成對應的unicode來進行傳輸運算。

String採用一種更靈活的方式進行儲存。在String中,一個英文字元佔1個位元組,而中文字元根據編碼的不同所佔位元組數也不同。在UTF-8編碼下,一箇中文字元佔3個位元組;而使用GBK編碼時一箇中文字元佔2個位元組。測試程式碼如下

中文並不一定是佔兩個位元組的,具體佔多少位元組是跟具體的編碼方式相關的。 比如說:GB2312、GBK、GB18030 編碼是佔用兩個位元組的,但是 UTF-8 編碼的話至少需要佔用三個位元組。

解釋一下MVC 以及 MVC Spring

MVC

MVC模式是軟體工程中的一種軟體架構模式,把軟體系統分為三個基本部分:模型、檢視和控制器。

mvc是一種建構網站的方法或思想,設計理念。mvc不是框架,而框架是基於mvc思想。

在早期java web,主要使用jsp + java bean模式,jsp與java bean產生嚴重耦合。出現了前端後端相互依賴的問題。

於是出現了servlet + jsp + java bean。 servlet是controller jsp是view 各自java bean 是model 對於後端來說,由於控制器和模型層的分離使得許多程式碼可以重用。 mvc的經典框架 struts1/struts2和作為模型層的hibernate紛紛出現。

  • springmvc中傳統的模型層被拆分成業務層service和資料訪問層dao。
  • 在service下可以通過spring的宣告式事務運算元據訪問層,而在業務層還允許訪問nosql。
  • Spring MVC通過一套MVC註解,讓POJO普通java類成為處理請求的控制器,而無需實現任何介面。
  • 支援REST風格的URL請求
  • 採用鬆散耦合可插拔元件結構,比其他MVC框架更具擴充套件性和靈活性

spring mvc元件和流程圖

執行原理

  1. spring mvc將所有的請求都提交給DispatcherServlet,它會委託應用系統的其他模組負責對請求 進行真正的處理工作。
  2. DispatcherServlet查詢一個或多個HandlerMapping,找到處理請求的Controller.
  3. DispatcherServlet請請求提交到目標Controller
  4. Controller進行業務邏輯處理後,會返回一個ModelAndView
  5. Dispathcher查詢一個或多個ViewResolver檢視解析器,找到ModelAndView物件指定的檢視物件
  6. 檢視物件負責渲染返回給客戶端。

面試 Q&A (二)

  • 使用者發起請求到前端控制器(Controller)
  • 前端控制器沒有處理業務邏輯的能力,需要找到具體的模型物件處理(Handler),到處理器對映器(HandlerMapping)中查詢Handler物件(Model)。
  • HandlerMapping返回執行鏈,包含了2部分內容: ① Handler物件、② 攔截器陣列
  • 前端處理器通過處理器介面卡包裝後執行Handler物件。
  • 處理業務邏輯。
  • Handler處理完業務邏輯,返回ModelAndView物件,其中view是檢視名稱,不是真正的檢視物件。
  • 將ModelAndView返回給前端控制器。
  • 檢視解析器(ViewResolver)返回真正的檢視物件(View)。
  • (此時前端控制器中既有檢視又有Model物件資料)前端控制器根據模型資料和檢視物件,進行檢視渲染。
  • 返回渲染後的檢視(html/json/xml)返回。
  • 給使用者產生響應。

在瀏覽器輸入url發生了什麼?

假如輸入maps.google.com

1. 瀏覽器會檢查dns記錄快取,找到url對應的ip地址

dns:domain name system 儲存域名和連線的ip地址,每一個url都有唯一的ip地址。

為了找到dns記錄,瀏覽器會依次檢查以下4種快取

  • 檢查瀏覽器快取,瀏覽器為您以前訪問過的網站維護一個固定期限的DNS記錄儲存庫。因此,它是第一個執行DNS查詢的地方。
  • 瀏覽器檢查系統快取。如果在瀏覽器快取中找不到,瀏覽器將向底層計算機OS發出系統呼叫(即Windows上的gethostname)以獲取記錄,因為OS還維護DNS記錄的快取。
  • 檢查路由器快取。如果在你的電腦上找不到的話,瀏覽器就會與維護自己的DNS記錄快取的路由器進行通訊。
  • 它檢查ISP快取。如果所有的步驟都失敗了,瀏覽器將轉移到ISP。您的ISP維護它自己的DNS伺服器,它包含一個DNS記錄的快取,瀏覽器將會檢查詢到您請求的URL的最後希望。

Internet Service Provider,簡稱ISP

2. 如果請求的URL不在快取中,ISP的DNS伺服器將發起一個DNS查詢,以查詢託管maps.google.com的伺服器的IP地址。

為了讓我的計算機連線到託管maps.google.com的伺服器,我需要maps.google.com的IP地址。DNS查詢的目的是在internet上搜尋多個DNS伺服器,直到找到網站的正確IP地址。這種型別的搜尋被稱為遞迴搜尋,因為搜尋將在DNS伺服器和DNS伺服器之間重複進行,直到找到我們需要的IP地址,或者返回一個錯誤響應說無法找到它為止。

在這種情況下,我們將把ISP的DNS伺服器稱為DNS遞迴器,它的職責是通過詢問internet上的其他DNS伺服器來找到想要的域名的正確IP地址。其他DNS伺服器稱為名稱伺服器,因為它們基於網站域名的域架構執行DNS搜尋。

面試 Q&A (二)

3. 瀏覽器啟動與伺服器的TCP連線

HTTP協議即超文字傳送協議(Hypertext Transfer Protocol ),是Web聯網的基礎,也是手機聯網常用的協議之一,HTTP協議是建立在TCP協議之上的一種應用。

一旦瀏覽器接收到正確的IP地址,它將與匹配IP地址以傳輸資訊的伺服器建立TCP連線。三次握手。

為了在您的計算機(客戶端)和伺服器之間傳輸資料包,建立一個TCP連線非常重要。這個連線是通過一個叫做TCP/IP三方握手的過程建立的。這是一個三個步驟,其中客戶端和伺服器交換SYN(同步)和ACK(確認)訊息來建立連線。

面試 Q&A (二)

為了保證服務端能收接受到客戶端的資訊並能做出正確的應答而進行前兩次(第一次和第二次)握手,為了保證客戶端能夠接收到服務端的資訊並能做出正確的應答而進行後兩次(第二次和第三次)握手。

面試 Q&A (二)

4. 瀏覽器傳送http請求

比如get請求 localhost:8080

Host: localhost:8080
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:60.0) Gecko/20100101 Firefox/60.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Cookie: JSESSIONID=8051806AE26B8CAB93BA03AC32A2191E; JSESSIONID=63AB1FE24ECF5F0930743468B802818B
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
複製程式碼

5. 伺服器處理請求並返回響應

該伺服器包含一個web伺服器(比如apache)。接收來自瀏覽器的請求並將其傳遞給請求處理程式以讀取和生成響應。請求處理程式是一個程式(用php等編寫)用於讀取請求、其頭部和cookies,以檢查所請求的內容,並在需要時更新伺服器上的資訊。然後它將以特定的格式(JSON、XML、HTML)組裝響應。

6. 伺服器傳送一個HTTP響應。

伺服器響應包含您請求的web頁面、狀態程式碼、壓縮型別(內容編碼)、如何快取頁面(快取控制)、要設定的任何cookie、隱私資訊等。

HTTP/1.1 200 
Content-Type: text/html;charset=UTF-8
Content-Language: zh-CN
Content-Length: 97
Date: Wed, 04 Jul 2018 08:04:54 GMT
複製程式碼

狀態碼:

  • 1xx代表一條資訊
  • 2xx說明訪問成功
  • 3xx將客戶重定向到其他url
  • 4xx客戶端發生錯誤
  • 5xx服務端發生錯誤 狀態碼詳細

7. 瀏覽器顯示html內容

瀏覽器分階段顯示HTML內容。首先,它將呈現裸骨HTML骨架。然後,它將檢查HTML標記併發出GET請求,請求web頁面上的其他元素,如影象、CSS樣式表、JavaScript檔案等。這些靜態檔案被瀏覽器快取,這樣下次訪問頁面時就不必再取回它們了。最後,你會看到網頁出現在你的瀏覽器上。

HashMap如何實現的?

  • hashmap是基於雜湊表即雜湊表的。

  • hashmap通過hashCode方法計算hash值,hash值是通過key物件來計算。hash值用來找到儲存Entry的正確位置。

  • hashmap使用equals方法來查詢在get()時要檢索的鍵的值,並在put()時查詢該鍵是否已經存在。

  • 衝突意味著有多個鍵擁有同樣的hash值,在這種情況下entry物件將會儲存在了同一個linkedlist裡。

    HashMap在java中使用內部 Node<K,V>來儲存對映。HashMap基於雜湊演算法,並在鍵上使用hashCode()和equals()方法進行get和put操作。

    HashMap使用單個連結串列來儲存元素,這些元素稱為bucket。當我們呼叫put方法時,將使用key的hashCode來確定儲存對映的bucket。

    一旦確定了bucket,就使用hashCode檢查是否已經有一個具有相同hashCode的鍵。如果存在一個具有相同hashCode的現有鍵,則在key上使用equals()方法。如果equals返回true,那麼value將被覆蓋,否則將對這個單獨連結的list bucket建立一個新的對映。如果沒有具有相同hashCode的鍵,則將對映插入到bucket中。

    hashmap 有一個表

    **
    * The table, resized as necessary. Length MUST Always be a power of two.
    */
    
       transient Node<K,V>[] table;
    複製程式碼

面試 Q&A (二)

    static class Node<K,V> implements Map.Entry<K,V> {
      final int hash;
      final K key;
      V value;
      Node<K,V> next;

      Node(int hash, K key, V value, Node<K,V> next) {
          this.hash = hash;
          this.key = key;
          this.value = value;
          this.next = next;
      }

      public final K getKey()        { return key; }
      public final V getValue()      { return value; }
      public final String toString() { return key + "=" + value; }

      public final int hashCode() {
          return Objects.hashCode(key) ^ Objects.hashCode(value);
      }

      public final V setValue(V newValue) {
          V oldValue = value;
          value = newValue;
          return oldValue;
      }

      public final boolean equals(Object o) {
          if (o == this)
              return true;
          if (o instanceof Map.Entry) {
              Map.Entry<?,?> e = (Map.Entry<?,?>)o;
              if (Objects.equals(key, e.getKey()) &&
                  Objects.equals(value, e.getValue()))
                  return true;
          }
          return false;
      }
  }
複製程式碼

put方法,注意連結串列中是紅黑樹的實現

面試 Q&A (二)

面試 Q&A (二)

TreeNode節點,這個類有非常多的方法
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
        TreeNode<K,V> parent;  // red-black tree links
        TreeNode<K,V> left;
        TreeNode<K,V> right;
        TreeNode<K,V> prev;    // needed to unlink next upon deletion
        boolean red;
        TreeNode(int hash, K key, V val, Node<K,V> next) {
            super(hash, key, val, next);
        }
}
複製程式碼
public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

    /**
     * Implements Map.put and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
複製程式碼

GC過程,GC是在什麼時候,對什麼東西,做了什麼事情?

gc需要完成的3件事情

  • 什麼時候回收?
  • 哪些記憶體需要回收?
  • 如何回收?

對堆進行回收之前首先要確定物件之中哪些還“存活”,哪些“死去”。

  1. 引用計數演算法

  2. 可達性分析演算法 這個演算法的基本思路就是通過一系列的稱為“GC Roots”的物件作為起始點,從這些節點開始向下搜尋,搜尋所走過的路徑稱為引用鏈(Reference Chain),當一個物件到GC Roots沒有任何引用鏈相連(用圖論的話來說,就是從GC Roots到這個物件不可達)時,則證明此物件是不可用的。

    面試 Q&A (二)

    在java中,可作為GC Roots的物件包括以下幾種:

    • 虛擬機器棧(棧幀中的本地變數表)中引用的物件
    • 方法區中類靜態屬性引用的物件
    • 方法區中常量引用的物件
    • 本地方法棧中JNI(Native方法)引用的物件。

強引用,軟引用,弱引用,虛引用

  • 強引用 :指在程式程式碼之中普遍存在的,類似Object obj=new Object()這類的引用,只要強引用還存在,垃圾收集器永遠不會回收掉被引用的物件。
  • 軟引用 :還有用但並非必需的物件。在系統將要發生記憶體溢位異常之前,將會把這些物件列進回收範圍之中進行第二次回收。如果這次回收還沒有足夠的記憶體,才會丟擲記憶體溢位異常。
  • 弱引用 :非必需物件,當垃圾收集器工作時,無論當前記憶體是否足夠,都會回收掉只被弱引用關聯的物件。
  • 虛引用 :幽靈引用或幻影引用,一個物件是否有虛引用的存在,完全不會對其生存時間構成影響,也無法通過虛引用來取得一個物件例項。為一個物件設定虛引用關聯的唯一目的就是能在這個物件被收集器回收時收到一個系統通知。

回收方法區(永久代)

永久代的垃圾回收主要回收兩部分

  • 廢棄常量
  • 無用的類:該類所有的例項都已經被回收,也就是java堆中不存在該類的任何例項,載入該類的ClassLoader已經被回收,該類對應的java.lang.Class物件沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。
  • 在大量使用反射、動態代理、CGLib等ByteCode框架、動態生成JSP以及OSGi這類頻繁自定義ClassLoader的場景都需要虛擬機器具備類解除安裝的功能,以保證永久代不會溢位。

垃圾收集演算法

1. 標記-清除演算法

最基礎的收集演算法,"mark-sweep"標記-清除演算法。

面試 Q&A (二)

演算法分為“標記”和“清除”兩個階段:首先標記出所有需要回收的物件,在標記完成後統一回收所有被標記的物件,它的標記過程其實在前一節講述物件標記判定時已經介紹過了。之所以說它是最基礎的收集演算法,是因為後續的收集演算法都是基於這種思路並對其不足進行改進而得到的。

它的主要不足有兩個:一個是效率問題,標記和清除兩個過程的效率都不高;另一個是空間問題,標記清除之後會產生大量不連續的記憶體碎片,空間碎片太多可能會導致以後在程式執行過程中需要分配較大物件時,無法找到足夠的連續記憶體而不得不提前觸發另一次垃圾收集動作。

2. 複製演算法

為了解決效率問題,一種稱為“複製”(Copying)的收集演算法出現了,它將可用記憶體按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的記憶體用完了,就將還存活著的物件複製到另外一塊上面,然後再把已使用過的記憶體空間一次清理掉。這樣使得每次都是對整個半區進行記憶體回收,記憶體分配時也就不用考慮記憶體碎片等複雜情況,只要移動堆頂指標,按順序分配記憶體即可,實現簡單,執行高效。只是這種演算法的代價是將記憶體縮小為了原來的一半,未免太高了一點。

面試 Q&A (二)

新生代中的物件98%是“朝生夕死”的,所以並不需要按照1:1的比例來劃分記憶體空間,而是將記憶體分為一塊較大的Eden空間兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor。當回收時,將Eden和Survivor中還存活著的物件一次性地複製到另外一塊Survivor空間上,最後清理掉Eden和剛才用過的Survivor空間。HotSpot虛擬機器預設Eden和Survivor的大小比例是8:1,也就是每次新生代中可用記憶體空間為整個新生代容量的90%(80%+10%),只有10%的記憶體會被“浪費”。當然,98%的物件可回收只是一般場景下的資料,我們沒有辦法保證每次回收都只有不多於10%的物件存活,當Survivor空間不夠用時,需要依賴其他記憶體(這裡指老年代)進行分配擔保(Handle Promotion)。

面試 Q&A (二)
記憶體的分配擔保就好比我們去銀行借款,如果我們信譽很好,在98%的情況下都能按時償還,於是銀行可能會預設我們下一次也能按時按量地償還貸款,只需要有一個擔保人能保證如果我不能還款時,可以從他的賬戶扣錢,那銀行就認為沒有風險了。記憶體的分配擔保也一樣,如果另外一塊Survivor空間沒有足夠空間存放上一次新生代收集下來的存活物件時,這些物件將直接通過分配擔保機制進入老年代。

3. 標記-整理法

標記過程仍然與“標記-清除”演算法一樣,但後續步驟不是直接對可回收物件進行清理,而是讓所有存活的物件都向一端移動,然後直接清理掉端邊界以外的記憶體。

面試 Q&A (二)

4. 分代收集演算法Generational Collection

只是根據物件存活週期的不同將記憶體劃分為幾塊。一般是把Java堆分為新生代和老年代,這樣就可以根據各個年代的特點採用最適當的收集演算法。

在新生代中,每次垃圾收集時都發現有大批物件死去,只有少量存活,那就選用複製演算法,只需要付出少量存活物件的複製成本就可以完成收集。

而老年代中因為物件存活率高、沒有額外空間對它進行分配擔保,就必須使用“標記—清理”或者“標記—整理”演算法來進行回收。

垃圾收集器

如果說收集演算法是記憶體回收的方法論,那麼垃圾收集器就是記憶體回收的具體實現

面試 Q&A (二)

Minor GC 和 Full GC

  • 新生代GC(Minor GC):指發生在新生代的垃圾收集動作,因為Java物件大多都具備朝生夕滅的特性,所以Minor GC非常頻繁,一般回收速度也比較快。具體原理見上一篇文章。
  • 老年代GC(Major GC / Full GC):指發生在老年代的GC,出現了Major GC,經常會伴隨至少一次的Minor GC(但非絕對的,在Parallel Scavenge收集器的收集策略裡就有直接進行Major GC的策略選擇過程)。Major GC的速度一般會比Minor GC慢10倍以上。

記憶體分配與回收策略

  • 物件優先在Eden分配 大多數情況下,物件在新生代Eden區分配,等eden區沒有足夠空間進行分配,虛擬機器將會發起一次Minor GC
  • 大物件直接進入老年代 所謂大物件是指,需要大量連續記憶體空間的java物件,最典型的就是那種很長的字串和陣列。
  • 長期存活的物件將進入老年代 為了確定哪些是老年代,虛擬機器給每個物件定義了一個物件年齡計數器。物件在Survivor區中每熬過一次Minor GC年齡就增加1.
  • 空間分配擔保 在發生Minor GC之前,虛擬機器會先檢查老年代最大可用的連續空間是否大於新生代所有物件總空間,如果這個條件成立,那麼Minor GC可以確保是安全的。如果不成立,則虛擬機器會檢視HandlePromotionFailure設定值是否允許擔保失敗。如果允許,那麼會繼續檢查老年代最大可用的連續空間是否大於歷次晉升到老年代物件的平均大小,如果大於,將嘗試著進行一次Minor GC,儘管這次Minor GC是有風險的;如果小於,或者HandlePromotionFailure設定不允許冒險,那這時也要改為進行一次Full GC

什麼時候發生回收?

eden滿了minor gc,升到老年代的物件大於老年代剩餘空間full gc,或者小於時被HandlePromotionFailure引數強制full gc;gc與非gc時間耗時超過了GCTimeRatio的限制引發OOM,調優諸如通過NewRatio控制新生代老年代比例,通過MaxTenuringThreshold控制進入老年前生存次數等

參考:icyfenix.iteye.com/blog/715301

資料庫慢查詢

慢sql特點 1 資料庫CPU負載高。一般是查詢語句中有很多計算邏輯,導致資料庫cpu負載。 2 IO負載高導致伺服器卡住。這個一般和全表查詢沒索引有關係。 3 查詢語句正常,索引正常但是還是慢。如果表面上索引正常,但是查詢慢,需要看看是否索引沒有生效。

開啟MySQL的慢查詢日誌來進一步定位問題。mysql提供了慢查詢日誌,日誌會記錄所有執行時間超過long_query_time的sql

要開啟日誌,需要在MySQL的配置檔案的mysqlld項下配置慢查詢日誌開啟。

有些SQL雖然出現在慢查詢日誌中,但未必是其本身的效能問題,可能是因為鎖等待,伺服器壓力高等等。

需要分析SQL語句真實的執行計劃,而不是看重新執行一遍SQL時,花費了多少時間,由自帶的慢查詢日誌或者開源的慢查詢系統定位到具體的出問題的SQL,然後使用Explain工具來逐步調優,瞭解 MySQL 在執行這條資料時的一些細節,比如是否進行了優化、是否使用了索引等等。基於 Explain 的返回結果我們就可以根據 MySQL 的執行細節進一步分析是否應該優化搜尋、怎樣優化索引。

總結

  1. 開啟慢日誌查詢,確定是否有SQL語句佔用了過多資源,如果是,在不改變業務原意的前提下,對insert、group by、order by、join等語句進行優化。
  2. 考慮調整MySQL的系統引數:innodb_buffer_pool_size、innodb_log_file_size、table_cache等。
  3. 確定是否是因為高併發引起行鎖的超時問題。
  4. 如果資料量過大,需要考慮進一步的分庫分表。

資料庫inner join,left outer join,right outer join

兩個表,customers,orders

customers

cust_id cust_name cust_address cust_city cust_state cust_zip cust_country cust_contact cust_email
10001 Coyote Inc. 200 Maple Lane Detroit MI 44444 USA Y Lee ylee@coyote.com
10002 Mouse House 333 Fromage Lane Columbus OH 43333 USA Jerry Mouse NULL
10003 Wascals 1 Sunny Place Muncie IN 42222 USA Jim Jones rabbit@wascally.com
10004 Yosemite Place 829 Riverside Drive Phoenix AZ 88888 USA Y Sam sam@yosemite.com
10005 E Fudd 4545 53rd Street Chicago IL 54545 USA E Fudd NULL

內部聯結

orders

order_num order_date cust_id
20005 2005-09-01 00:00:00 10001
20006 2005-09-12 00:00:00 10003
20007 2005-09-30 00:00:00 10004
20008 2005-10-03 00:00:00 10005
20009 2005-10-08 00:00:00 10001
SELECT customers.cust_id,orders.order_num FROM customers INNER JOIN orders ON customers.cust_id = orders.cust_id;
複製程式碼
cust_id order_num
10001 20005
10001 20009
10003 20006
10004 20007
10005 20008

外部聯結

SELECT customers.cust_id,orders.order_num FROM customers LEFT OUTER JOIN orders ON customers.cust_id = orders.cust_id
複製程式碼
cust_id order_num
10001 20005
10001 20009
10002 NULL
10003 20006
10004 20007
10005 20008
SELECT customers.cust_id,orders.order_num FROM customers RIGHT OUTER JOIN orders ON customers.cust_id = orders.cust_id
複製程式碼
cust_id order_num
10001 20005
10001 20009
10003 20006
10004 20007
10005 20008

在使用OUTER JOIN語法時,必須使用RIGHTLEFT關鍵字 指定包括其所有行的表(RIGHT指出的是OUTER JOIN右邊的表,而LEFT指出的是OUTER JOIN左邊的表)。上面的例子使用LEFT OUTER JOINFROM子句的左邊表(customers表)中選擇所有行。為了從右邊的表中選擇所有行,應該使用RIGHT OUTER JOIN.

解釋一下索引

當表的資料量比較大時,查詢操作會比較耗時。建立索引是加快查詢速度的有效手段。資料庫索引類似於圖書後面的索引,能快速定位到需要查詢的內容。

資料庫索引有多種型別,常見索引包括順序檔案上的索引b+樹索引雜湊索引點陣圖索引全文索引

在mysql中,儲存引擎先在索引中找到對應值,然後根據匹配的索引記錄找到對應的資料行。

mysql先在索引上按值進行查詢,然後返回所有包含該值的資料行。

索引可以包含一個或多個列的值。如果索引包含多個列,那麼列的順序也十分重要,因為mysql只能高效地使用索引的最左字首列。

索引可以讓伺服器快速定位到表的指定位置 索引的優點:

  1. 索引大大減少了伺服器需要掃描的資料量
  2. 索引可以幫助伺服器避免排序和臨時表
  3. 索引可以讓隨機I/O變成順序I/O

資料庫優化

資料庫優化總結

  • 選取最適用的欄位屬性
  • 使用join來代替子查詢
  • 使用聯合union來代替手動建立的臨時表
  • 使用事務
  • 鎖定表

schema模式與資料型別優化

mysql支援的資料型別非常多,選擇正確的資料型別對於獲得高效能至關重要。不管儲存哪種型別的資料,下面幾個簡單的原則都有助於作出更好選擇。

  • 更小,一般情況下儘量使用可以正確儲存資料的最小資料型別。
  • 簡單就好,簡單資料型別需要更少的cpu週期。
  • 儘量避免null

使用索引

索引的代價:1.需要佔硬碟空間 2.一旦插入新的資料,需要重新建索引 高效能索引策略

  • 獨立的列,獨立的列”是指索引列不能是表示式的一部分,也不能是函式的引數
  • 字首索引和索引選擇性
  • 多列索引
  • 選擇合適的索引列順序
  • 聚簇索引

查詢效能優化

優化資料訪問

  • 是否向資料庫請求了不需要的資料
    • mysql是否掃描額外的記錄
      • 查詢消耗:響應時間,掃描的行數,返回的行數

重構查詢的方式

優化伺服器設定

作業系統和硬體優化

層次遍歷

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> res = new ArrayList<>();
        if(root == null){
            return res;
        }
        Queue<TreeNode> qp = new LinkedList<>();
        qp.offer(root);
        
        while(!qp.isEmpty()){
            List<Integer> level = new ArrayList<>();
            TreeNode node = null;
            int lenL = qp.size();
            for(int i = 0; i < lenL; i++){
                node = qp.poll();
                level.add(node.val);
                if(node.left != null){
                    qp.offer(node.left);
                }
                
                if(node.right != null){
                    qp.offer(node.right);
                }
            }
            res.add(level);
        }
        return res;
    }
}
複製程式碼

相關文章