阿里Java開發手冊[泰山版] 關鍵速記 ?

   

剛剛過了一下阿里Java開發手冊,發現很多內容都滿重要.這裏挑了比較重要,或者容易踩的坑進行了記錄.

看到條數不要害怕,其實很多都是常識,爲了加深一下印象而已.

 

 

Table of Contents

1.POJO 類中布爾類型變量都不要加 is 前綴,否則部分框架解析會引起序列化錯誤。

2.Object 的 equals 方法容易拋空指針異常,應使用常量或確定有值的對象來調用equals。

3.所有整型包裝類對象之間值的比較,全部使用 equals 方法比較。

4.浮點數之間的等值判斷,基本數據類型不能用==來比較,包裝數據類型不能用 equals 來判斷。

5.定義數據對象 DO 類時,屬性類型要與數據庫字段類型相匹配。

6.禁止使用構造方法 BigDecimal(double)的方式把 double 值轉化爲 BigDecimal 對象。

7.慎改serialVersionUID 字段

8.關於 hashCode 和 equals 的處理,遵循如下規則:

9.使用集合轉數組的方法,必須使用集合的 toArray(T[] array),傳入的是類型完全一致、長度爲 0 的空數組。

10.在使用 Collection 接口任何實現類的 addAll()方法時,都要對輸入的集合參數進行 NPE 判斷。

11. 使用工具類 Arrays.asList()把數組轉換成集合時,不能使用其修改集合相關的方法,它的 add/remove/clear 方法會拋出 UnsupportedOperationException 異常。

12. 不要在 foreach 循環裏進行元素的 remove/add 操作。

13. 集合初始化時,指定集合初始值大小

14. 使用 entrySet 遍歷 Map 類集合 KV,而不是 keySet 方式進行遍歷。

15.  Map 類集合 K/V 能不能存儲 null 值的情況

16. 線程池不允許使用 Executors 去創建

17. SimpleDateFormat 是線程不安全的類

18. 必須回收自定義的 ThreadLocal 變量

19. 在使用嘗試機制來獲取鎖的方式中,進入業務代碼塊之前,必須先判斷當前線程是否持有鎖。鎖的釋放規則與鎖的阻塞等待方式相同

20. 併發修改同一記錄時,避免更新丟失, 需要加鎖。 

21.Timer 異常情況注意

22. CountDownLatch 異常情況注意

23.避免 Random 實例被多線程使用

25.volatile 解決多線程內存不可見問題

26.當 switch 括號內的變量類型爲 String 並且此變量爲外部參數時,必須先進行 null 判斷。

27.在高併發場景中,避免使用”等於”判斷作爲中斷或退出的條件。

28.注意 Math.random() 這個方法返回是 double 類型

29.獲取當前毫秒數

30.日期格式化時,傳入 pattern 中表示年份統一使用小寫的 y。

31.finally 塊必須對資源對象、流對象進行關閉,有異常也要做 try-catch

33.防止 NPE,注意 NPE 產生的場景:

34.日誌存儲規約

35.在日誌輸出時,字符串變量之間的拼接使用佔位符的方式。

36.避免重複打印日誌,浪費磁盤空間,務必在 log4j.xml 中設置 additivity=false。

37.生產環境禁止輸出 debug 日誌

38.單元測試中不準使用 System.out 來進行人肉驗證,必須使用 assert 來驗證。

39.小數類型爲 decimal,禁止使用 float 和 double。

40.varchar優化

41.單錶行數超過 500 萬行或者單表容量超過 2GB,才推薦進行分庫分表。

42.業務上具有唯一特性的字段,即使是多個字段的組合,也必須建成唯一索引。

43.超過三個表禁止 join。

44.在 varchar 字段上建立索引時,必須指定索引長度,沒必要對全字段建立索引,根據實際文本區分度決定索引長度即可。

45.頁面搜索嚴禁左模糊或者全模糊,如果需要請走搜索引擎來解決。

46.利用延遲關聯或者子查詢優化超多分頁場景。

47.SQL 性能優化的目標:至少要達到 range 級別,要求是 ref 級別,如果可以是 consts 最好。

48.不要使用 count(列名)或 count(常量)來替代 count(*).

49.count(distinct col) 計算該列除 NULL 之外的不重複行數

50.當某一列的值全是 NULL 時,count(col)的返回結果爲 0,但 sum(col)的返回結果爲 NULL,因此使用 sum()時需注意 NPE 問題。

51.使用 ISNULL()來判斷是否爲 NULL 值。

52. 不得使用外鍵與級聯,一切外鍵概念必須在應用層解決。

53.禁止使用存儲過程,存儲過程難以調試和擴展,更沒有移植性

54.in 操作能避免則避免,若實在避免不了,需要仔細評估 in 後邊的集合元素數量,控制在 1000 個之內。

55.如果有國際化需要,所有的字符存儲與表示,均以 utf-8 編碼,注意字符統計函數的區別。

56.TRUNCATE 不建議在開發代碼中使用。

57.在表查詢中,一律不要使用 * 作爲查詢的字段列表

58.POJO 類的布爾屬性不能加 is,而數據庫字段必須加 is_,要求在 resultMap 中進行字段與屬性之間的映射。

59. sql.xml 配置參數使用:#{},#param# 不要使用${} 此種方式容易出現 SQL 注入。

60.@Transactional 事務不要濫用。


 

 

1.POJO 類中布爾類型變量都不要加 is 前綴,否則部分框架解析會引起序列化錯誤。

說明:在本文 MySQL 規約中的建表約定第一條,表達是與否的值採用 is_xxx 的命名方式,所以,需要在<resultMap>設置從 is_xxx 到 xxx 的映射關係。

反例:定義爲基本數據類型 Boolean isDeleted 的屬性,它的方法也是 isDeleted(),RPC 框架在反向解析的時候,“誤以爲”對應的屬性名稱是 deleted,導致屬性獲取不到,進而拋出異常。

 

2.Object 的 equals 方法容易拋空指針異常,應使用常量或確定有值的對象來調用equals。

 

正例:"test".equals(object);

反例:object.equals("test");

說明:推薦使用 java.util.Objects#equals(JDK7 引入的工具類)。

 

3.所有整型包裝類對象之間值的比較,全部使用 equals 方法比較。


說明:對於 Integer var = ? 在-128 至 127 範圍內的賦值,Integer 對象是在 IntegerCache.cache 產生,會複用已有對象,這個區間內的 Integer 值可以直接使用==進行判斷,但是這個區間之外的所有數據,都會在堆上產生,並不會複用已有對象,這是一個大坑,推薦使用 equals 方法進行判斷。

4.浮點數之間的等值判斷,基本數據類型不能用==來比較,包裝數據類型不能用 equals 來判斷。

說明:浮點數採用“尾數+階碼”的編碼方式,類似於科學計數法的“有效數字+指數”的表示方式。二進制無法精確表示大部分的十進制小數

反例:

float a = 1.0f - 0.9f;
float b = 0.9f - 0.8f;
if (a == b) {
    // 預期進入此代碼快,執行其它業務邏輯
    // 但事實上 a==b 的結果爲 false
}
Float x = Float.valueOf(a);
Float y = Float.valueOf(b);
if (x.equals(y)) {
/    / 預期進入此代碼快,執行其它業務邏輯
    // 但事實上 equals 的結果爲 false
}

 

正例:

1.指定一個誤差範圍,兩個浮點數的差值在此範圍之內,則認爲是相等的。

    float a = 1.0f - 0.9f;     
    float b = 0.9f - 0.8f;     
    float diff = 1e-6f; 
 
    if (Math.abs(a - b) < diff) { 
        System.out.println("true"); 
    } 

 

2.使用 BigDecimal 來定義值,再進行浮點數的運算操作。

    BigDecimal a = new BigDecimal("1.0"); 
    BigDecimal b = new BigDecimal("0.9"); 
    BigDecimal c = new BigDecimal("0.8"); 
 
    BigDecimal x = a.subtract(b); 
    BigDecimal y = b.subtract(c); 
 
    if (x.equals(y)) { 
        System.out.println("true"); 
    } 

 

 

5.定義數據對象 DO 類時,屬性類型要與數據庫字段類型相匹配。

正例:數據庫字段的 bigint 必須與類屬性的 Long 類型相對應。

反例:某個案例的數據庫表 id 字段定義類型 bigint unsigned,實際類對象屬性爲 Integer,隨着 id 越來越大,超過 Integer 的表示範圍而溢出成爲負數。

 

6.禁止使用構造方法 BigDecimal(double)的方式把 double 值轉化爲 BigDecimal 對象。

說明:BigDecimal(double)存在精度損失風險,在精確計算或值比較的場景中可能會導致業務邏輯異常。

正例:優先推薦入參爲 String 的構造方法,或使用 BigDecimal 的 valueOf 方法,此方法內部其實執行了 Double 的 toString,而 Double 的 toString 按 double 的實際能表達的精度對尾數進行了截斷。

 代碼示例如下:

        BigDecimal g = new BigDecimal(0.1f);
        BigDecimal recommend1 = new BigDecimal("0.1");
        BigDecimal recommend2 = BigDecimal.valueOf(0.1);
        System.out.println(g);
        System.out.println(recommend1);
        System.out.println(recommend2);

------------- 輸出結果 --------------------------------------------

        0.100000001490116119384765625
        0.1
        0.1

 

7.慎改serialVersionUID 字段

序列化類新增屬性時,請不要修改 serialVersionUID 字段,避免反序列失敗;如果完全不兼容升級,避免反序列化混亂,那麼請修改 serialVersionUID 值。

說明:注意 serialVersionUID 不一致會拋出序列化運行時異常。

 

8.關於 hashCode 和 equals 的處理,遵循如下規則:

1.只要覆寫 equals,就必須覆寫 hashCode。

2.因爲 Set 存儲的是不重複的對象,依據 hashCode 和 equals 進行判斷,所以 Set 存儲的對象必須覆寫這兩個方法。

3.如果自定義對象作爲 Map 的鍵,那麼必須覆寫 hashCode 和 equals。說明:String 已覆寫 hashCode 和 equals 方法,所以我們可以愉快地使用 String 對象作爲 key 來使用。

 

9.使用集合轉數組的方法,必須使用集合的 toArray(T[] array),傳入的是類型完全一致、長度爲 0 的空數組。

反例:直接使用 toArray 無參方法存在問題,此方法返回值只能是 Object[]類,若強轉其它類型數組將出

現 ClassCastException 錯誤。

正例:

List<String> list = new ArrayList<>(2); 
list.add("guan"); 
list.add("bao");

String[] array = list.toArray(new String[0]);     

說明:使用 toArray 帶參方法,數組空間大小的 length:

 

1.等於 0,動態創建與 size 相同的數組,性能最好。

2.大於 0 但小於 size,重新創建大小等於 size 的數組,增加 GC 負擔。

3.等於 size,在高併發情況下,數組創建完成之後,size 正在變大的情況下,負面影響與上相同。

4.大於 size,空間浪費,且在 size 處插入 null 值,存在 NPE 隱患。

 

 

10.在使用 Collection 接口任何實現類的 addAll()方法時,都要對輸入的集合參數進行 NPE 判斷。

說明:在 ArrayList#addAll 方法的第一行代碼即 Object[] a = c.toArray(); 其中 c 爲輸入集合參數,如果爲 null,則直接拋出異常。

 

11. 使用工具類 Arrays.asList()把數組轉換成集合時,不能使用其修改集合相關的方法,它的 add/remove/clear 方法會拋出 UnsupportedOperationException 異常。

說明:asList 的返回對象是一個 Arrays 內部類,並沒有實現集合的修改方法。Arrays.asList 體現的是適配器模式,只是轉換接口,後臺的數據仍是數組。

        String[] str = new String[] { "yang", "hao" };
        List list = Arrays.asList(str);

        //報錯 Exception java.lang.UnsupportedOperationException
        list.add("yangguanbao");

        // 會更改 list中的結果
        str[0] = "changed";

        System.out.println(list.get(0));

 

12. 不要在 foreach 循環裏進行元素的 remove/add 操作。

remove 元素請使用 Iterator 方式,如果併發操作,需要對 Iterator 對象加鎖。

正例:

        List<String> list = new ArrayList<>();
        list.add("1");
        list.add("2");


        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            String item = iterator.next();

            if ("1".equalsIgnoreCase(item)) {
                iterator.remove();
            }
        }

反例:

        List<String> list = new ArrayList<>();
        list.add("1");
        list.add("2");

        for (String item : list) {
            if ("1".equals(item)) {
                list.remove(item);
            }
        }

說明:以上代碼的執行結果, 移除"1," 不會報錯 . 移除"2", 拋出:  java.util.ConcurrentModificationException

 

13. 集合初始化時,指定集合初始值大小

說明:HashMap 使用 HashMap(int initialCapacity) 初始化。

正例:initialCapacity = (需要存儲的元素個數 / 負載因子) + 1。注意負載因子(即 loader factor)默認爲 0.75,如果暫時無法確定初始值大小,請設置爲 16(即默認值)。

反例:HashMap 需要放置 1024 個元素,由於沒有設置容量初始大小,隨着元素不斷增加,容量 7 次被迫擴大,resize 需要重建 hash 表,嚴重影響性能。

 

 

14. 使用 entrySet 遍歷 Map 類集合 KV,而不是 keySet 方式進行遍歷。

說明:keySet 其實是遍歷了 2 次,一次是轉爲 Iterator 對象,另一次是從 hashMap 中取出 key 所對應的 value。

而 entrySet 只是遍歷了一次就把 key 和 value 都放到了 entry 中,效率更高。如果是 JDK8,使用 Map.forEach 方法。

正例:values()返回的是 V 值集合,是一個 list 集合對象;keySet()返回的是 K 值集合,是一個 Set 集合對象;entrySet()返回的是 K-V 值組合集合。

        Map map = new HashMap();
        map.put(1,2);
        map.put(2,2);
        map.put(3,2);
        map.put(4,2);

        map.forEach(new BiConsumer() {
            @Override
            public void accept(Object o, Object o2) {
                System.out.println(o + "=>" +  o2);
            }
        });

        map.forEach((k,v)->{
            System.out.println(k + " = > " + v);
        });

15.  Map 類集合 K/V 能不能存儲 null 值的情況

集合類

Key

Value

Super

說明

Hashtable

不允許爲 null

不允許爲 null

Dictionary

線程安全

ConcurrentHashMap

不允許爲 null

不允許爲 null

AbstractMap

鎖分段技術(JDK8:CAS)

TreeMap

不允許爲 null

允許爲 null

AbstractMap

線程不安全

HashMap

允許爲 null

允許爲 null

AbstractMap

線程不安全


反例:由於 HashMap 的干擾,很多人認爲 ConcurrentHashMap 是可以置入 null 值,而事實上,存儲 null 值時會拋出 NPE 異常。

 

16. 線程池不允許使用 Executors 去創建

線程池不允許使用 Executors 去創建,而是通過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。

說明:Executors 返回的線程池對象的弊端如下:

1.FixedThreadPool 和 SingleThreadPool:

  允許的請求隊列長度爲 Integer.MAX_VALUE,可能會堆積大量的請求,從而導致 OOM。

2.CachedThreadPool:

  允許的創建線程數量爲 Integer.MAX_VALUE,可能會創建大量的線程,從而導致 OOM。

 

 

17. SimpleDateFormat 是線程不安全的類

  1. SimpleDateFormat 是線程不安全的類,一般不要定義爲 static 變量,如果定義爲 static,必須加鎖,或者使用 DateUtils 工具類。正例:注意線程安全,使用 DateUtils。亦推薦如下處理:
     private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
        @Override
        protected DateFormat initialValue() {
           return new SimpleDateFormat("yyyy-MM-dd");
        }
    };

說明:如果是 JDK8 的應用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar,

DateTimeFormatter 代替 SimpleDateFormat,官方給出的解釋:simple beautiful strong immutable thread-safe。

        LocalDateTime rightNow= LocalDateTime.now();
        String date=DateTimeFormatter.ISO_LOCAL_DATE.format(rightNow);
        System.out.println(date);
        
        DateTimeFormatter formatter=DateTimeFormatter.ofPattern("YYYY-MM-dd HH:mm:ss");
        System.out.println(formatter.format(rightNow));

18. 必須回收自定義的 ThreadLocal 變量

必須回收自定義的 ThreadLocal 變量,尤其在線程池場景下,線程經常會被複用,如果不清理自定義的 ThreadLocal 變量,可能會影響後續業務邏輯和造成內存泄露等問題。

儘量在代理中使用 try-finally 塊進行回收。

正例:

        objectThreadLocal.set(userInfo);
    
        try {
            // ...
            //
        }  finally {
            objectThreadLocal.remove();
        }

19. 在使用嘗試機制來獲取鎖的方式中,進入業務代碼塊之前,必須先判斷當前線程是
否持有鎖。鎖的釋放規則與鎖的阻塞等待方式相同

 

說明:Lock 對象的 unlock 方法在執行時,它會調用 AQS 的 tryRelease 方法(取決於具體實現類),如果
當前線程不持有鎖,則拋出 IllegalMonitorStateException 異常

正例:

    public static void main(String[] args) {

        Lock lock = new ReentrantLock();
        // ...
        boolean isLocked = lock.tryLock();
        if (isLocked) {
            try {
                //todo
            } finally {
                lock.unlock();
            }
        }


    }

 

 

 

20. 併發修改同一記錄時,避免更新丟失, 需要加鎖。 

併發修改同一記錄時,避免更新丟失, 需要加鎖。 要麼在應用層加鎖,要麼在緩存加鎖,要麼在數據庫層使用樂觀鎖,使用 version 作爲更新依據。
說明:如果每次訪問衝突概率小於 20%,推薦使用樂觀鎖,否則使用悲觀鎖。樂觀鎖的重試次數不得小於3 次

 

21.Timer 異常情況注意

多線程並行處理定時任務時,Timer 運行多個 TimeTask 時,只要其中之一沒有捕獲拋出的異常,其它任務便會自動終止運行,如果在處理定時任務時使用ScheduledExecutorService 則沒有這個問題。  

 

22. CountDownLatch 異常情況注意

使用 CountDownLatch 進行異步轉同步操作,每個線程退出前必須調用 countDown 方法,線程執行代碼注意 catch 異常,確保 countDown 方法被執行到,避免主線程無法執行至 await 方法,直到超時才返回結果。

說明:注意,子線程拋出異常堆棧,不能在主線程 try-catch 到。

 

23.避免 Random 實例被多線程使用

避免 Random 實例被多線程使用,雖然共享該實例是線程安全的,但會因競爭同一 seed 導致的性能下降。

說明:Random 實例包括 java.util.Random 的實例或者 Math.random()的方式。

正例:在 JDK7 之後,可以直接使用 API ThreadLocalRandom,而在 JDK7 之前,需要編碼保證每個線程持有一個實例。

 

24. Double-Checked Locking is Broken

在併發場景下,通過雙重檢查鎖(double-checked locking)實現延遲初始化的優化

問題隱患(可參考 The "Double-Checked Locking is Broken" Declaration),推薦解決方案中較爲簡單一種(適用於 JDK5 及以上版本),將目標屬性聲明爲 volatile 型。

 

    public class LazyInitDemo {
        // 注意一定要加volatile 
        private  volatile   Object helper = null;
        public Object getHelper() {
            if (helper == null) {
                synchronized(this) {
                    if (helper == null)
                        helper = new Object();
                }

            }
            return helper;
        }
    }

 

25.volatile 解決多線程內存不可見問題

對於一寫多讀,是可以解決變量同步問題,但是如果多寫,同樣無法解決線程安全問題。

說明:如果是 count++操作,使用如下類實現:AtomicInteger count = new AtomicInteger(); count.addAndGet(1); 如果是 JDK8,推薦使用 LongAdder 對象,比 AtomicLong 性能更好(減少樂觀鎖的重試次數)。

 

26.當 switch 括號內的變量類型爲 String 並且此變量爲外部參數時,必須先進行 null 判斷。

 

   public static void main(String[] args) {
        method(null);
    }


    public static void method(String param) {
        switch (param) {   // 在這行會直接報錯
            // 肯定不是進入這裏 
            case "sth":
            System.out.println("it's sth");                 break;
            // 也不是進入這裏
            case "null":
            System.out.println("it's null");                 break;
            // 也不是進入這裏
            default:
            System.out.println("default");
        }
    }

----------------------------------------------

Exception in thread "main" java.lang.NullPointerException
	at com.alibaba.SwitchTest.method(SwitchTest.java:10)
	at com.alibaba.SwitchTest.main(SwitchTest.java:5)

 

 

27.在高併發場景中,避免使用”等於”判斷作爲中斷或退出的條件。

在高併發場景中,避免使用”等於”判斷作爲中斷或退出的條件。

說明:如果併發控制沒有處理好,容易產生等值判斷被“擊穿”的情況,使用大於或小於的區間判斷條件來代替。

反例:判斷剩餘獎品數量等於 0 時,終止發放獎品,但因爲併發處理錯誤導致獎品數量瞬間變成了負數,這樣的話,活動無法終止。

 

28.注意 Math.random() 這個方法返回是 double 類型

注意 Math.random() 這個方法返回是 double 類型,注意取值的範圍 0≤x<1(能夠取到零值,注意除零異常),如果想獲取整數類型的隨機數,不要將 x 放大 10 的若干倍然後取整,直接使用 Random 對象的 nextInt 或者 nextLong 方法。

 

29.獲取當前毫秒數

獲取當前毫秒數 System.currentTimeMillis(); 而不是 new Date().getTime();

說明:如果想獲取更加精確的納秒級時間值,使用 System.nanoTime()的方式。

在 JDK8 中,針對統計時間等場景,推薦使用 Instant 類。

 

 

30.日期格式化時,傳入 pattern 中表示年份統一使用小寫的 y。

日期格式化時,傳入 pattern 中表示年份統一使用小寫的 y。

說明:日期格式化時,yyyy表示當天所在的年,而大寫的YYYY代表是week in which year

(JDK7 之後引入的概念),意思是當天所在的周屬於的年份,一週從週日開始,週六結束,只要本週跨年,返回的YYYY就是下一年。另外需要注意:

    • 表示月份是大寫的 M
    • 表示分鐘則是小寫的 m
    • 24 小時制的是大寫的 H
    • 12 小時制的則是小寫的 h

正例:表示日期和時間的格式如下所示:

 new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  

 

31.finally 塊必須對資源對象、流對象進行關閉,有異常也要做 try-catch

說明:如果 JDK7 及以上,可以使用 try-with-resources 方式。

 

32.不要在 finally 塊中使用 return。

說明:try 塊中的 return 語句執行成功後,並不馬上返回,而是繼續執行 finally 塊中的語句,如果此處存在 return 語句,則在此直接返回,無情丟棄掉 try 塊中的返回點。

     private int x = 0;

    public int checkReturn() {
        try {
            // x 等於 1,此處不返回
            return ++x;
        } finally {
            // 返回的結果是 2
            return ++x;
        }
    }

 

33.防止 NPE,注意 NPE 產生的場景:

返回類型爲基本數據類型,return 包裝數據類型的對象時,自動拆箱有可能產生 NPE。

    反例:public int f() { return Integer 對象}, 如果爲 null,自動解箱拋 NPE。

  1. 數據庫的查詢結果可能爲 null。
  2. 集合裏的元素即使 isNotEmpty,取出的數據元素也可能爲 null。
  3. 遠程調用返回對象時,一律要求進行空指針判斷,防止 NPE。
  4. 對於 Session 中獲取的數據,建議進行 NPE 檢查,避免空指針。
  5. 級聯調用 obj.getA().getB().getC();一連串調用,易產生 NPE。

正例:使用 JDK8 的 Optional 類來防止 NPE 問題。

 

34.日誌存儲規約

所有日誌文件至少保存 15 天,因爲有些異常具備以“周”爲頻次發生的特點。網絡運行狀態、安全相關信息、系統監測、管理後臺操作、用戶敏感操作需要留存相關的網絡日誌不少於 6 個月。

 

 

35.在日誌輸出時,字符串變量之間的拼接使用佔位符的方式。

說明:因爲 String 字符串的拼接會使用 StringBuilder 的 append()方式,有一定的性能損耗。使用佔位符僅是替換動作,可以有效提升性能。

正例:logger.debug("Processing trade with id: {} and symbol: {}", id, symbol);

 

 

 

36.避免重複打印日誌,浪費磁盤空間,務必在 log4j.xml 中設置 additivity=false。

正例:<logger name="com.taobao.dubbo.config" additivity="false">  

 

37.生產環境禁止輸出 debug 日誌

謹慎地記錄日誌。生產環境禁止輸出 debug 日誌;有選擇地輸出 info 日誌;如果使用 warn 來記錄剛上線時的業務行爲信息,一定要注意日誌輸出量的問題,避免把服務器磁盤撐爆,並記得及時刪除這些觀察日誌。

說明:大量地輸出無效日誌,不利於系統性能提升,也不利於快速定位錯誤點。記錄日誌時請思考:這些日誌真的有人看嗎?看到這條日誌你能做什麼?能不能給問題排查帶來好處

如非必要,請不要在此場景打出 error 級別,避免頻繁報警。 

可以使用 warn 日誌級別來記錄用戶輸入參數錯誤的情況,避免用戶投訴時,無所適從。

 

38.單元測試中不準使用 System.out 來進行人肉驗證,必須使用 assert 來驗證。

 

 

39.小數類型爲 decimal,禁止使用 float 和 double。

說明:在存儲的時候,float 和 double 都存在精度損失的問題,很可能在比較值的時候,得到不正確的結果。如果存儲的數據範圍超過 decimal 的範圍,建議將數據拆成整數和小數並分開存儲。

 

40.varchar優化

varchar 是可變長字符串,不預先分配存儲空間,長度不要超過 5000,如果存儲長度大於此值,定義字段類型爲 text,獨立出來一張表,用主鍵來對應,避免影響其它字段索引效率。

 

41.單錶行數超過 500 萬行或者單表容量超過 2GB,才推薦進行分庫分表。

說明:如果預計三年後的數據量根本達不到這個級別,請不要在創建表時就分庫分表。

 

42.業務上具有唯一特性的字段,即使是多個字段的組合,也必須建成唯一索引。

說明:不要以爲唯一索引影響了 insert 速度,這個速度損耗可以忽略,但提高查找速度是明顯的;另外,即使在應用層做了非常完善的校驗控制,只要沒有唯一索引,根據墨菲定律,必然有髒數據產生。

 

43.超過三個表禁止 join。

需要 join 的字段,數據類型必須絕對一致;多表關聯查詢時,保證被關聯的字段需要有索引。

說明:即使雙表 join 也要注意表索引、SQL 性能。

 

44.在 varchar 字段上建立索引時,必須指定索引長度,沒必要對全字段建立索引,根據實際文本區分度決定索引長度即可。

說明:索引的長度與區分度是一對矛盾體,一般對字符串類型數據,長度爲 20 的索引,區分度會高達

90%以上,可以使用 count(distinct left(列名, 索引長度))/count(*)的區分度來確定。

 

 

45.頁面搜索嚴禁左模糊或者全模糊,如果需要請走搜索引擎來解決。

說明:索引文件具有 B-Tree 的最左前綴匹配特性,如果左邊的值未確定,那麼無法使用此索引。

 

 

 

46.利用延遲關聯或者子查詢優化超多分頁場景。

說明:MySQL 並不是跳過 offset 行,而是取 offset+N 行,然後返回放棄前 offset 行,返回 N 行,那當 offset 特別大的時候,效率就非常的低下,要麼控制返回的總頁數,要麼對超過特定閾值的頁數進行 SQL 改寫。

正例:先快速定位需要獲取的 id 段,然後再關聯:

      SELECT a.* FROM 表 1 a, (select id from 表 1 where 條件 LIMIT 100000,20 ) b where a.id=b.id

 

 

 

47.SQL 性能優化的目標:至少要達到 range 級別,要求是 ref 級別,如果可以是 consts 最好。

說明:

  1. consts 單表中最多隻有一個匹配行(主鍵或者唯一索引),在優化階段即可讀取到數據。
  2. ref 指的是使用普通的索引(normal index)。
  3. range 對索引進行範圍檢索。

反例:explain 表的結果,type=index,索引物理文件全掃描,速度非常慢,這個 index 級別比較 range 還低,與全表掃描是小巫見大巫。

 

48.不要使用 count(列名)或 count(常量)來替代 count(*).

count(*)是 SQL92 定義的標準統計行數的語法,跟數據庫無關,跟 NULL 和非 NULL 無關。

說明:count(*)會統計值爲 NULL 的行,而 count(列名)不會統計此列爲 NULL 值的行。

 

49.count(distinct col) 計算該列除 NULL 之外的不重複行數

注意 count(distinct col1, col2) 如果其中一列全爲 NULL,那麼即使另一列有不同的值,也返回爲 0。

 

50.當某一列的值全是 NULL 時,count(col)的返回結果爲 0,但 sum(col)的返回結果爲 NULL,因此使用 sum()時需注意 NPE 問題。

正例:使用如下方式來避免 sum 的 NPE 問題:SELECT IFNULL(SUM(column), 0) FROM table;

51.使用 ISNULL()來判斷是否爲 NULL 值。

說明:NULL 與任何值的直接比較都爲 NULL。

  1. NULL<>NULL 的返回結果是 NULL,而不是 false。
  2. NULL=NULL 的返回結果是 NULL,而不是 true。
  3. NULL<>1 的返回結果是 NULL,而不是 true。

 

52. 不得使用外鍵與級聯,一切外鍵概念必須在應用層解決。

說明:以學生和成績的關係爲例,學生表中的 student_id 是主鍵,那麼成績表中的 student_id 則爲外鍵。如果更新學生表中的 student_id,同時觸發成績表中的 student_id 更新,即爲級聯更新。

外鍵與級聯更新適用於單機低併發,不適合分佈式、高併發集羣;級聯更新是強阻塞,存在數據庫更新風暴的風險;

外鍵影響數據庫的插入速度。

53.禁止使用存儲過程,存儲過程難以調試和擴展,更沒有移植性

  

54.in 操作能避免則避免,若實在避免不了,需要仔細評估 in 後邊的集合元素數量,控制在 1000 個之內。

 

55.如果有國際化需要,所有的字符存儲與表示,均以 utf-8 編碼,注意字符統計函數的區別。

說明:

      SELECT LENGTH("輕鬆工作"); 返回爲 12

      SELECT CHARACTER_LENGTH("輕鬆工作"); 返回爲 4

      如果需要存儲表情,那麼選擇 utf8mb4 來進行存儲,注意它與 utf-8 編碼的區別。

 

56.TRUNCATE 不建議在開發代碼中使用。

TRUNCATE TABLE 比 DELETE 速度快,且使用的系統和事務日誌資源少,但TRUNCATE 無事務且不觸發 trigger,有可能造成事故,故不建議在開發代碼中使用此語句。

說明:TRUNCATE TABLE 在功能上與不帶 WHERE 子句的 DELETE 語句相同。

 

57.在表查詢中,一律不要使用 * 作爲查詢的字段列表

在表查詢中,一律不要使用 * 作爲查詢的字段列表,需要哪些字段必須明確寫明。

說明:

1)增加查詢分析器解析成本。

2)增減字段容易與 resultMap 配置不一致。

3)無用字段增加網絡消耗,尤其是 text 類型的字段。

 

58.POJO 類的布爾屬性不能加 is,而數據庫字段必須加 is_,要求在 resultMap 中進行字段與屬性之間的映射。

說明:參見定義 POJO 類以及數據庫字段定義規定,在<resultMap>中增加映射,是必須的。在

MyBatis Generator 生成的代碼中,需要進行對應的修改。

 

59. sql.xml 配置參數使用:#{},#param# 不要使用${} 此種方式容易出現 SQL 注入。

 

 

60.@Transactional 事務不要濫用。

事務會影響數據庫的 QPS,另外使用事務的地方需要考慮各方面的回滾方案,包括緩存回滾、搜索引擎回滾、消息補償、統計修正等。

 

 

 

 

  

 

 

 

   

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章