PostgreSql詳細介紹(四)

PostgreSQL 學習手冊(索引) 
一、索引的類型: 
 
    PostgreSQL 提供了多  種索引類型:B-Tree、Hash、GiST 和 GIN,由於它們使用了不同的算法,因此每種索引類型都有其 適合的查詢類型,缺省時,CREATE INDEX 命令將創建 B-Tree 索引。

         1. B-Tree:

    CREATE TABLE test1 (

        id integer,

        content varchar

    );

    CREATE INDEX test1_id_index ON test1 (id);

        B-Tree 索引主要用於等於和範圍查詢,特別是當索引列包含操作符" <、<=、=、>=和>"作爲查詢條件時,PostgreSQL 的查 詢規劃器都會考慮使用 B-Tree 索引。在使用 BETWEEN、IN、 IS NULL 和 IS NOT NULL 的查詢中,PostgreSQL 也可以使用 B-Tree 索引。然而對於基於模式匹配操作符的查詢,如 LIKE、ILIKE、~和 ~*,僅當模式存在一個常量,且該常量位於模式字符串的開頭 時,如 col LIKE 'foo%'或 col ~ '^foo',索引纔會生效,否則將會執行全表掃描,如:col LIKE '%bar'。

          2. Hash:

    CREATE INDEX name ON table USING hash (column);  

  散列(Hash)索引只能處理簡單的等於比較。當索引列使用等於操作符進行比較時,查詢規劃器會考慮使用散列索引。     這裏需要額外說明的是,PostgreSQL 散列索引的性能不比 B-Tree 索引強,但是散列索引的尺寸和構造時間則更差。另外,由於 散列索引操作目前沒有記錄 WAL 日誌,因此一旦發生了數據庫崩潰,我們將不得不用 REINDEX 重建散列索引。

         3. GiST:     GiST 索引不是一種單獨的索引類型,而是一種架構,可以在該架構上實現很多不同的索引策略。從而可以使 GiST 索引根據不同 的索引策略,而使用特定的操作符類型。

          4. GIN:     GIN 索引是反轉索引,它可以處理包含多個鍵的值(比如數組)。與 GiST 類似,GIN 同樣支持用戶定義的索引策略,從而可以使 GIN 索引根據不同的索引策略,而使用特定的操作符類型。作爲示例,PostgreSQL 的標準發佈中包含了用於一維數組的 GIN 操作 符類型,如:<@、@>、=、&&等。 
 
二、複合索引: 
 
    PostgreSQL 中的索引可以定義在數據表的多個字段上,如:

    CREATE TABLE test2 (

        major int,

        minor int,

        name varchar

    }

    CREATE INDEX test2_mm_idx ON test2 (major, minor);

    在當前的版本中,只有 B-tree、GiST 和 GIN 支持複合索引,其中最多可以聲明 32 個字段。

    1. B-Tree 類型的複合索引:

    在 B-Tree 類型的複合索引中,該索引字段的任意子集均可用於查詢條件,不過,只有當複合索引中的第一個索引字段(最左邊)被 包含其中時,纔可以獲得最高效率。
    2. GiST 類型的複合索引:

    在 GiST 類型的複合索引中,只有當第一個索引字段被包含在查詢條件中時,才能決定該查詢會掃描多少索引數據,而其他索引字 段上的條件只是會限制索引返回的條目。假如第一個索引字段上的大多數數據都有相同的鍵值,那麼此時應用 GiST 索引就會比較低 效。  
 
    3. GIN 類型的複合索引:

    與 B-Tree 和 GiST 索引不同的是,GIN 複合索引不會受到查詢條件中使用了哪些索引字段子集的影響,無論是哪種組合,都會得 到相同的效率。 
    使用複合索引應該謹慎。在大多數情況下,單一字段上的索引就已經足夠了,並且還節約時間和空間。除非表的使用模式非常固定, 否則超過三個字段的索引幾乎沒什麼用處。  
 
三、組合多個索引: 
 
    PostgreSQL 可以在查詢時組合多個索引(包括同一索引的多次使用),來處理單個索引掃描不能實現的場合。與此同時,系統還可 以在多個索引掃描之間組成 AND 和 OR 的條件。比如,一個類似 WHERE x = 42 OR x = 47 OR x = 53 OR x = 99 的查詢,可 以被分解成四個獨立的基於 x 字段索引的掃描,每個掃描使用一個查詢子句,之後再將這些掃描結果 OR 在一起並生成最終的結果。 另外一個例子是,如果我們在 x 和 y 上分別存在獨立的索引,那麼一個類似 WHERE x = 5 AND y = 6 的查詢,就會分別基於這兩 個字段的索引進行掃描,之後再將各自掃描的結果進行 AND 操作並生成最終的結果行。     爲了組合多個索引,系統掃描每個需要的索引,然後在內存裏組織一個 BITMAP,它將給出索引掃描出的數據在數據表中的物理 位置。然後,再根據查詢的需要,把這些位圖進行 AND 或者 OR 的操作並得出最終的 BITMAP。最後,檢索數據表並返回數據行。 表的數據行是按照物理順序進行訪問的,因爲這是位圖的佈局,這就意味着任何原來的索引的排序都將消失。如果查詢中有 ORDER BY 子句,那麼還將會有一個額外的排序步驟。因爲這個原因,以及每個額外的索引掃描都會增加額外的時間,這樣規劃器有時候就會選 擇使用簡單的索引掃描,即使有多個索引可用也會如此。      
     
四、唯一索引: 
 
    目前,只有 B-Tree 索引可以被聲明爲唯一索引。

    CREATE UNIQUE INDEX name ON table (column [, ...]);

    如果索引聲明爲唯一索引,那麼就不允許出現多個索引值相同的行。我們認爲 NULL 值相互間不相等。      
五、表達式索引: 
 
    表達式索引主要用於在查詢條件中存在基於某個字段的函數或表達式的結果與其他值進行比較的情況,如:

    SELECT * FROM test1 WHERE lower(col1) = 'value';

    此時,如果我們僅僅是在 col1 字段上建立索引,那麼該查詢在執行時一定不會使用該索引,而是直接進行全表掃描。如果該表的 數據量較大,那麼執行該查詢也將會需要很長時間。解決該問題的辦法非常簡單,在 test1 表上建立基於 col1 字段的表達式索引,如:

    CREATE INDEX test1_lower_col1_idx ON test1 (lower(col1));

    如果我們把該索引聲明爲 UNIQUE,那麼它會禁止創建那種 col1 數值只是大小寫有區別的數據行,以及 col1 數值完全相同的數 據行。因此,在表達式上的索引可以用於強制那些無法定義爲簡單唯一約束的約束。現在讓我們再看一個應用表達式索引的例子。

    SELECT * FROM people WHERE (first_name || ' ' || last_name) = 'John Smith';

    和上面的例子一樣,儘管我們可能會爲 first_name 和 last_name 分別創建獨立索引,或者是基於這兩個字段的複合索引,在執 行該查詢語句時,這些索引均不會被使用,該查詢能夠使用的索引只有我們下面創建的表達式索引。

    CREATE INDEX people_names ON people ((first_name || ' ' || last_name));  

  CREATE INDEX 命令的語法通常要求在索引表達式周圍書寫圓括弧,就像我們在第二個例子裏顯示的那樣。如果表達式只是一個 函數調用,那麼可以省略,就像我們在第一個例子裏顯示的那樣。     從索引維護的角度來看,索引表達式要相對低效一些,因爲在插入數據或者更新數據的時候,都必須爲該行計算表達式的結果,並 將該結果直接存儲到索引裏。然而在查詢時,PostgreSQL 就會把它們看做 WHERE idxcol = 'constant',因此搜索的速度等效於基 於簡單索引的查詢。通常而言,我們只是應該在檢索速度比插入和更新速度更重要的場景下使用表達式索引。       
六、部分索引: 
 
    部分索引(partial index)是建立在一個表的子集上的索引,而該子集是由一個條件表達式定義的(叫做部分索引的謂詞)。該索 引只包含表中那些滿足這個謂詞的行。     由於不是在所有的情況下都需要更新索引,因此部分索引會提高數據插入和數據更新的效率。然而又因爲部分索引比普通索引要小, 因此可以更好的提高確實需要索引部分的查詢效率。見以下三個示例:     1. 索引字段和謂詞條件字段一致:

    CREATE INDEX access_log_client_ip_ix ON access_log(client_ip)  

      WHERE NOT (client_ip > inet '192.168.100.0' AND client_ip < inet '192.168.100.255');    

下面的查詢將會用到該部分索引:

    SELECT * FROM access_log WHERE url = '/index.html' AND client_ip = inet '212.78.10.32';

    下面的查詢將不會用該部分索引:

    一個不能使用這個索引的查詢可以是∶

    SELECT * FROM access_log WHERE client_ip = inet '192.168.100.23'; 
 
    2. 索引字段和謂詞條件字段不一致:

    PostgreSQL 支持帶任意謂詞的部分索引,唯一的約束是謂詞的字段也要來自於同樣的數據表。注意,如果你希望你的查詢語句能 夠用到部分索引,那麼就要求該查詢語句的條件部分必須和部分索引的謂詞完全匹配。 準確說,只有在 PostgreSQL 能夠識別出該 查詢的 WHERE 條件在數學上涵蓋了該索引的謂詞時,這個部分索引才能被用於該查詢。

    CREATE INDEX orders_unbilled_index ON orders(order_nr) WHERE billed is not true;  

  下面的查詢一定會用到該部分索引:

    SELECT * FROM orders WHERE billed is not true AND order_nr < 10000;

    那麼對於如下查詢呢?

    SELECT * FROM orders WHERE billed is not true AND amount > 5000.00;

    這個查詢將不像上面那個查詢這麼高效,畢竟查詢的條件語句中沒有用到索引字段,然而查詢條件"billed is not true"卻和部分 索引的謂詞完全匹配,因此 PostgreSQL 將掃描整個索引。這樣只有在索引數據相對較少的情況下,該查詢才能更有效一些。     下面的查詢將不會用到部分索引。

    SELECT * FROM orders WHERE order_nr = 3501;  

       3. 數據表子集的唯一性約束:

    CREATE TABLE tests (

        subject text,

        target text,

        success boolean,

        ...

    );

    CREATE UNIQUE INDEX tests_success_constraint ON tests(subject, target) WHERE success;  

  該部分索引將只會對 success 字段值爲 true 的數據進行唯一性約束。在實際的應用中,如果成功的數據較少,而不成功的數據較 多時,該實現方法將會非常高效。       
七、檢查索引的使用: 
    見以下四條建議:

    1. 總是先運行 ANALYZE。     該命令將會收集表中數值分佈狀況的統計。在估算一個查詢返回的行數時需要這個信息,而規劃器則需要這個行數以便給每個可能 的查詢規劃賦予真實的開銷值。如果缺乏任何真實的統計信息,那麼就會使用一些缺省數值,這樣肯定是不準確的。因此,如果還沒 有運行 ANALYZE 就檢查一個索引的使用狀況,那將會是一次失敗的檢查。

     2. 使用真實的數據做實驗。     用測試數據填充數據表,那麼該表的索引將只會基於測試數據來評估該如何使用索引,而不是對所有的數據都如此使用。比如從 100000 行中選 1000 行,規劃器可能會考慮使用索引,那麼如果從 100 行中選 1 行就很難說也會使用索引了。因爲 100 行的數據 很可能是存儲在一個磁盤頁面中,然而沒有任何查詢規劃能比通過順序訪問一個磁盤頁面更加高效了。與此同時,在模擬測試數據時也要注意,如果這些數據是非常相似的數據、完全隨機的數據,或按照排序順序插入的數據,都會令統計信息偏離實際數據應該具有 的特徵。

    3. 如果索引沒有得到使用,那麼在測試中強制它的使用也許會有些價值。有一些運行時參數可以關閉各種各樣的查詢規劃。         4. 強制使用索引用法將會導致兩種可能:一是系統選擇是正確的,使用索引實際上並不合適,二是查詢計劃的開銷計算並不能反 映現實情況。這樣你就應該對使用和不使用索引的查詢進行計時,這個時候 EXPLAIN ANALYZE 命令就很有用了。 
 
PostgreSQL 學習手冊(事物隔離)  
    在 SQL 的標準中事物隔離級別分爲以下四種:

    1. 讀未提交(Read uncommitted)

    2. 讀已提交(Read committed)  

    3. 可重複讀(Repeatable read)

    4. 可串行化(Serializable)

    然而 PostgreSQL 在 9.1 之前的版本中只是實現了其中兩種,即讀已提交和可串行化,如果在實際應用中選擇了另外兩種,那麼 PostgreSQL 將會自動向更嚴格的隔離級別調整。在 PostgreSQL v9.1 的版本中提供了三種實現方式,即在原有的基礎上增加了可 重複讀。在這篇博客中我們將只是針對 2)和 4)進行說明和比較,因爲在 9.1 中,3)和 4)的差別也是非常小的。 
  


    最後需要說明的是,在絕大多數的情況下,讀已提交級別均可適用,而且該級別的併發效率更高。只有在比較特殊的情況下,才手 工將當前的事物隔離級別調整爲可串行化或可重複讀。 
PostgreSQL 學習手冊(性能提升技巧) 
一、使用 EXPLAIN: 
 
    PostgreSQL 爲每個查詢都生成一個查詢規劃,因爲選擇正確的查詢路徑對性能的影響是極爲關鍵的。PostgreSQL 本身已經包 含了一個規劃器用於尋找最優規劃,我們可以通過使用 EXPLAIN 命令來查看規劃器爲每個查詢生成的查詢規劃。     PostgreSQL 中生成的查詢規劃是由 1 到 n 個規劃節點構成的規劃樹,其中最底層的節點爲表掃描節點,用於從數據表中返回檢 索出的數據行。然而,不同的掃描節點類型代表着不同的表訪問模式,如:順序掃描、索引掃描,以及位圖索引掃描等。如果查詢仍 然需要連接、聚集、排序,或者是對原始行的其它操作,那麼就會在掃描節點"之上"有其它額外的節點。並且這些操作通常都有多種 方法,因此在這些位置也有可能出現不同的節點類型。EXPLAIN 將爲規劃樹中的每個節點都輸出一行信息,顯示基本的節點類型和規 劃器爲執行這個規劃節點計算出的預計開銷值。第一行(最上層的節點)是對該規劃的總執行開銷的預計,這個數值就是規劃器試圖最 小化的數值。

     這裏有一個簡單的例子,如下:

    EXPLAIN SELECT * FROM tenk1;

                             QUERY PLAN

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

     Seq Scan on tenk1  (cost=0.00..458.00 rows=10000 width=244)

          EXPLAIN 引用的數據是:

    1). 預計的啓動開銷(在輸出掃描開始之前消耗的時間,比如在一個排序節點裏做排續的時間)。

    2). 預計的總開銷。

    3). 預計的該規劃節點輸出的行數。

    4). 預計的該規劃節點的行平均寬度(單位:字節)。

     這裏開銷(cost)的計算單位是磁盤頁面的存取數量,如 1.0 將表示一次順序的磁盤頁面讀取。其中上層節點的開銷將包括其所有子 節點的開銷。這裏的輸出行數(rows)並不是規劃節點處理/掃描的行數,通常會更少一些。一般而言,頂層的行預計數量會更接近於 查詢實際返回的行數。     現在我們執行下面基於系統表的查詢:
    SELECT relpages, reltuples FROM pg_class WHERE relname = 'tenk1';     從查詢結果中可以看出 tenk1 表佔有 358 個磁盤頁面和 10000 條記錄,然而爲了計算 cost 的值,我們仍然需要知道另外一個系 統參數值。

    postgres=# show cpu_tuple_cost;

     cpu_tuple_cost

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

   0.01

    (1 row)  

   cost = 358(磁盤頁面數) + 10000(行數) * 0.01(cpu_tuple_cost 系統參數值)

           下面我們再來看一個帶有 WHERE 條件的查詢規劃。

    EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 7000;

                                  QUERY PLAN

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

   Seq Scan on tenk1  (cost=0.00..483.00 rows=7033 width=244)

       Filter: (unique1 < 7000)

         EXPLAIN 的輸出顯示,WHERE 子句被當作一個"filter"應用,這表示該規劃節點將掃描表中的每一行數據,之後再判定它們是否 符合過濾的條件,最後僅輸出通過過濾條件的行數。這裏由於 WHERE 子句的存在,預計的輸出行數減少了。即便如此,掃描仍將訪 問所有 10000 行數據,因此開銷並沒有真正降低,實際上它還增加了一些因數據過濾而產生的額外 CPU 開銷。     上面的數據只是一個預計數字,即使是在每次執行 ANALYZE 命令之後也會隨之改變,因爲 ANALYZE 生成的統計數據是通過從 該表中隨機抽取的樣本計算的。     如果我們將上面查詢的條件設置的更爲嚴格一些的話,將會得到不同的查詢規劃,如:     EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 100; 
 
                                      QUERY PLAN

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

     Bitmap Heap Scan on tenk1  (cost=2.37..232.35 rows=106 width=244)

       Recheck Cond: (unique1 < 100)

       ->  Bitmap Index Scan on tenk1_unique1  (cost=0.00..2.37 rows=106 width=0)

             Index Cond: (unique1 < 100)

         這裏,規劃器決定使用兩步規劃,最內層的規劃節點訪問一個索引,找出匹配索引條件的行的位置,然後上層規劃節點再從表裏讀 取這些行。單獨地讀取數據行比順序地讀取它們的開銷要高很多,但是因爲並非訪問該表的所有磁盤頁面,因此該方法的開銷仍然比一次順序掃描的開銷要少。這裏使用兩層規劃的原因是因爲上層規劃節點把通過索引檢索出來的行的物理位置先進行排序,這樣可以 最小化單獨讀取磁盤頁面的開銷。節點名稱裏面提到的"位圖(bitmap)"是進行排序的機制。     現在我們還可以將 WHERE 的條件設置的更加嚴格,如:

    EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 3; 
 
                                      QUERY PLAN  

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

   Index Scan using tenk1_unique1 on tenk1  (cost=0.00..10.00 rows=2 width=244)        Index Cond: (unique1 < 3)

         在該 SQL 中,表的數據行是以索引的順序來讀取的,這樣就會令讀取它們的開銷變得更大,然而事實上這裏將要獲取的行數卻少 得可憐,因此沒有必要在基於行的物理位置進行排序了。     現在我們需要向 WHERE 子句增加另外一個條件,如:     EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 3 AND stringu1 = 'xxx'; www.linuxidc.com
                                           QUERY PLAN

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

   Index Scan using tenk1_unique1 on tenk1  (cost=0.00..10.01 rows=1 width=244)        Index Cond: (unique1 < 3)  

     Filter: (stringu1 = 'xxx'::name)  

       新增的過濾條件 stringu1 = 'xxx'只是減少了預計輸出的行數,但是並沒有減少實際開銷,因爲我們仍然需要訪問相同數量的數 據行。而該條件並沒有作爲一個索引條件,而是被當成對索引結果的過濾條件來看待。     如果 WHERE 條件裏有多個字段存在索引,那麼規劃器可能會使用索引的 AND 或 OR 的組合,如:

    EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 100 AND unique2 > 9000;  

                                             QUERY PLAN

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

     Bitmap Heap Scan on tenk1  (cost=11.27..49.11 rows=11 width=244)  

     Recheck Cond: ((unique1 < 100) AND (unique2 > 9000))

       ->  BitmapAnd  (cost=11.27..11.27 rows=11 width=0)  

           ->  Bitmap Index Scan on tenk1_unique1  (cost=0.00..2.37 rows=106 width=0)

                   Index Cond: (unique1 < 100)

             ->  Bitmap Index Scan on tenk1_unique2  (cost=0.00..8.65 rows=1042 width=0)

                   Index Cond: (unique2 > 9000)

         這樣的結果將會導致訪問兩個索引,與只使用一個索引,而把另外一個條件只當作過濾器相比,這個方法未必是更優。      現在讓我們來看一下基於索引字段進行表連接的查詢規劃,如:

    EXPLAIN SELECT * FROM tenk1 t1, tenk2 t2 WHERE t1.unique1 < 100 AND t1.unique2 = t2.unique2;                                                  QUERY PLAN

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

     Nested Loop  (cost=2.37..553.11 rows=106 width=488)

       ->  Bitmap Heap Scan on tenk1 t1  (cost=2.37..232.35 rows=106 width=244)

             Recheck Cond: (unique1 < 100)

             ->  Bitmap Index Scan on tenk1_unique1  (cost=0.00..2.37 rows=106 width=0)

                   Index Cond: (unique1 < 100)

       ->  Index Scan using tenk2_unique2 on tenk2 t2  (cost=0.00..3.01 rows=1 width=244)

             Index Cond: ("outer".unique2 = t2.unique2)

         從查詢規劃中可以看出(Nested Loop)該查詢語句使用了嵌套循環。外層的掃描是一個位圖索引,因此其開銷與行計數和之前查 詢的開銷是相同的,這是因爲條件 unique1 < 100 發揮了作用。 這個時候 t1.unique2 = t2.unique2 條件子句還沒有產生什麼作 用,因此它不會影響外層掃描的行計數。然而對於內層掃描而言,當前外層掃描的數據行將被插入到內層索引掃描中,並生成類似的 條件 t2.unique2 = constant。所以,內層掃描將得到和 EXPLAIN SELECT * FROM tenk2 WHERE unique2 = 42 一樣的計劃 和開銷。最後,以外層掃描的開銷爲基礎設置循環節點的開銷,再加上每個外層行的一個迭代(這裏是 106 * 3.01),以及連接處理 需要的一點點 CPU 時間。         如果不想使用嵌套循環的方式來規劃上面的查詢,那麼我們可以通過執行以下系統設置,以關閉嵌套循環,如:

    SET enable_nestloop = off;

    EXPLAIN SELECT * FROM tenk1 t1, tenk2 t2 WHERE t1.unique1 < 100 AND t1.unique2 = t2.unique2;                                                    QUERY PLAN

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

     Hash Join  (cost=232.61..741.67 rows=106 width=488) www.linuxidc.com
       Hash Cond: ("outer".unique2 = "inner".unique2)  

     ->  Seq Scan on tenk2 t2  (cost=0.00..458.00 rows=10000 width=244)

       ->  Hash  (cost=232.35..232.35 rows=106 width=244)

             ->  Bitmap Heap Scan on tenk1 t1  (cost=2.37..232.35 rows=106 width=244)

                   Recheck Cond: (unique1 < 100)

                   ->  Bitmap Index Scan on tenk1_unique1  (cost=0.00..2.37 rows=106 width=0)  

                       Index Cond: (unique1 < 100)

         這個規劃仍然試圖用同樣的索引掃描從 tenk1 裏面取出符合要求的 100 行,並把它們存儲在內存中的散列(哈希)表裏,然後對 tenk2 做一次全表順序掃描,併爲每一條 tenk2 中的記錄查詢散列(哈希)表,尋找可能匹配 t1.unique2 = t2.unique2 的行。讀取 tenk1 和建立散列表是此散列聯接的全部啓動開銷,因爲我們在開始讀取 tenk2 之前不可能獲得任何輸出行。 
 
    此外,我們還可以用 EXPLAIN ANALYZE 命令檢查規劃器預估值的準確性。這個命令將先執行該查詢,然後顯示每個規劃節點內 實際運行時間,以及單純 EXPLAIN 命令顯示的預計開銷,如:

    EXPLAIN ANALYZE SELECT * FROM tenk1 t1, tenk2 t2 WHERE t1.unique1 < 100 AND t1.unique2 = t2.unique2;                                                                       QUERY PLAN

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

     Nested Loop  (cost=2.37..553.11 rows=106 width=488) (actual time=1.392..12.700 rows=100 loops=1)

       ->  Bitmap Heap Scan on tenk1 t1  (cost=2.37..232.35 rows=106 width=244) (actual time=0.878..2.367 rows=100 loops=1)              Recheck Cond: (unique1 < 100)

             ->  Bitmap Index Scan on tenk1_unique1  (cost=0.00..2.37 rows=106 width=0) (actual time=0.546..0.546 rows=100 loops=1)

                   Index Cond: (unique1 < 100)

       ->  Index Scan using tenk2_unique2 on tenk2 t2  (cost=0.00..3.01 rows=1 width=244) (actual time=0.067..0.078 rows=1 loops=100)

             Index Cond: ("outer".unique2 = t2.unique2)

     Total runtime: 14.452 ms

         注意"actual time"數值是以真實時間的毫秒來計算的,而"cost"預估值是以磁盤頁面讀取數量來計算的,所以它們很可能是不一 致的。然而我們需要關注的只是兩組數據的比值是否一致。

    在一些查詢規劃裏,一個子規劃節點很可能會運行多次,如之前的嵌套循環規劃,內層的索引掃描會爲每個外層行執行一次。在這 種情況下,"loops"將報告該節點執行的總次數,而顯示的實際時間和行數目則是每次執行的平均值。這麼做的原因是令這些真實數值 與開銷預計顯示的數值更具可比性。如果想獲得該節點所花費的時間總數,計算方式是用該值乘以"loops"值。     EXPLAIN ANALYZE 顯示的"Total runtime"包括執行器啓動和關閉的時間,以及結果行處理的時間,但是它並不包括分析、重 寫或者規劃的時間。

    如果 EXPLAIN 命令僅能用於測試環境,而不能用於真實環境,那它就什麼用都沒有。比如,在一個數據較少的表上執行 EXPLAIN, 它不能適用於數量很多的大表,因爲規劃器的開銷計算不是線性的,因此它很可能對大些或者小些的表選擇不同的規劃。一個極端的例子是一個只佔據一個磁盤頁面的表,在這樣的表上,不管它有沒有索引可以使用,你幾乎都總是得到順序掃描規劃。規劃器知道不 管在任何情況下它都要進行一個磁盤頁面的讀取,所以再增加幾個磁盤頁面讀取用以查找索引是毫無意義的。 
 
二、批量數據插入: 
 
    有以下幾種方法用於優化數據的批量插入。

    1. 關閉自動提交:

    在批量插入數據時,如果每條數據都被自動提交,當中途出現系統故障時,不僅不能保障本次批量插入的數據一致性,而且由於有多次提交操作的發生,整個插入效率也會受到很大的打擊。解決方法是,關閉系統的自動提交,並且在插入開始之前,顯示的執行 begin transaction 命令,在全部插入操作完成之後再執行 commit 命令提交所有的插入操作。

    2. 使用 COPY:

    使用 COPY 在一條命令裏裝載所有記錄,而不是一系列的 INSERT 命令。COPY 命令是爲裝載數量巨大的數據行優化過的,它不 像 INSERT 命令那樣靈活,但是在裝載大量數據時,系統開銷也要少很多。因爲 COPY 是單條命令,因此在填充表的時就沒有必要關 閉自動提交了。

     3. 刪除索引:

    如果你正在裝載一個新創建的表,最快的方法是創建表,用 COPY 批量裝載,然後創建表需要的任何索引。因爲在已存在數據的 表上創建索引比維護逐行增加要快。當然在缺少索引期間,其它有關該表的查詢操作的性能將會受到一定的影響,唯一性約束也有可 能遭到破壞。

         4. 刪除外鍵約束:

    和索引一樣,"批量地"檢查外鍵約束比一行行檢查更加高效。因此,我們可以先刪除外鍵約束,裝載數據,然後在重建約束。          5. 增大 maintenance_work_mem:

    在裝載大量數據時,臨時增大 maintenance_work_mem 系統變量的值可以改進性能。這個系統參數可以提高 CREATE INDEX 命令和 ALTER TABLE ADD FOREIGN KEY 命令的執行效率,但是它不會對 COPY 操作本身產生多大的影響。

         6. 增大 checkpoint_segments:

    臨時增大 checkpoint_segments 系統變量的值也可以提高大量數據裝載的效率。這是因爲在向 PostgreSQL 裝載大量數據時, 將會導致檢查點操作(由系統變量 checkpoint_timeout 聲明)比平時更加頻繁的發生。在每次檢查點發生時,所有的髒數據都必須 flush 到磁盤上。通過提高 checkpoint_segments 變量的值,可以有效的減少檢查點的數目。

         7. 事後運行 ANALYZE:

    在增加或者更新了大量數據之後,應該立即運行 ANALYZE 命令,這樣可以保證規劃器得到基於該表的最新數據統計。換句話說, 如果沒有統計數據或者統計數據太過陳舊,那麼規劃器很可能會選擇一個較差的查詢規劃,從而導致查詢效率過於低下。 
PostgreSQL 學習手冊(服務器配置)  
一、服務器進程的啓動和關閉: 
 
    下面是 pg_ctl 命令的使用方法和常用選項,需要指出的是,該命令是 postgres 命令的封裝體,因此在使用上比直接使用 postgres 更加方便。

    pg_ctl init[db] [-D DATADIR] [-s] [-o "OPTIONS"]

    pg_ctl start     [-w] [-t SECS] [-D DATADIR] [-s] [-l FILENAME] [-o "OPTIONS"]

    pg_ctl stop     [-W] [-t SECS] [-D DATADIR] [-s] [-m SHUTDOWN-MODE]

    pg_ctl restart  [-w] [-t SECS] [-D DATADIR] [-s] [-m SHUTDOWN-MODE]  

   pg_ctl reload  [-D DATADIR] [-s]

   pg_ctl status  [-D DATADIR]

   pg_ctl promote [-D DATADIR] [-s] 


         這裏我們只是給出最爲常用的使用方式,即數據庫服務器的正常啓動和關閉。

    #start 表示啓動 postgres 服務器進程。
     #-D 指定數據庫服務器的初始目錄的存放路徑。
     #-l 指定數據庫服務器進程的日誌文件
     /> pg_ctl -w start -D /opt/PostgreSQL/9.1/data -l /opt/PostgreSQL/9.1/data/pg_log/startup.log

    #stop 表示停止 postgres 服務器進程
     #-m fast 在關閉系 統時,使用 fast 的關閉模式。
     /> pg_ctl stop -m fast -w -D /opt/PostgreSQL/9.1/data 
 
二、服務器配置: 
 
    1. 設置參數:     在 PostgreSQL 中,所有配置參數名都是大小寫不敏感的。每個參數都可以接受四種類型的值,它們分別是布爾、整數、浮點數 和字符串。其中布爾值可以是 ON、OFF、TRUE、FALSE、YES、NO、1 和 0。包含這些參數的配置文件是 postgresql.conf, 該文件通常存放在 initdb 初始化的數據(data)目錄下,見如下配置片段:

    # 這是一個註釋

    log_connections = yes

    log_destination = 'syslog'

    search_path = '$user, public'

    井號(#)開始的行爲註釋行,如果配置值中包含數字,則需要用單引號括起。如果參數值本身包含單引號,我們可以寫兩個單引號 (推薦方法)或用反斜扛包圍。

        這裏需要注意的是,並非所有配置參數都可以在服務器運行時執行動態修改,有些參數在修改後,只能等到服務器重新啓動後才能 生效。     PostgreSQL 還提供了另外一種修改配置參數的方法,即在命令行上直接執行修改命令,如:

    /> postgres -c log_connections=yes -c log_destination='syslog'

    如果此時命令行設置的參數和配置文件中的參數相互衝突,那麼命令行中給出的參數將覆蓋配置文件中已有的參數值。除此之外, 我們還可以通過ALTER DATABASE和ALTER USER等PostgreSQL的數據定義命令來分別修改指定數據庫或指定用戶的配置信息。 其中針對數據庫的設置將覆蓋任何從 postgres 命令行或者配置文件從給出的設置,然後又會被針對用戶的設置覆蓋,最後又都會被 每會話的選項覆蓋。下面是當服務器配置出現衝突時,PostgreSQL 服務器將會採用哪種方式的優先級,如:         1). 基於會話的配置;

2). 基於用戶的配置;

3). 基於數據庫的配置;
4). postgres 命令行指定的配置;

5). 配置文件 postgresql.conf 中給出的配置。

    最後需要說明的是,有些設置可以通過 PostgreSQL 的 set 命令進行設置,如在 psql 中我們可以輸入:

    SET ENABLE_SEQSCAN TO OFF;

    也可以通過 show 命令來顯示指定配置的當前值,如:

    SHOW ENABLE_SEQSCAN;

    與此同時,我們也可以手工查詢 pg_settings 系統表的方式來檢索感興趣的系統參數。  
 
三、內存相關的參數配置: 
 
    1. shared_buffers(integer):

    設置數據庫服務器可以使用的共享內存數量。缺省情況下可以設置爲 32MB,但是不要少於 128KB。因爲該值設置的越高對系統 的性能越有好處。該配置參數只能在數據庫啓動時設置。

    此時,如果你有一臺專用的數據庫服務器,其內存爲 1G 或者更多,那麼我們推薦將該值設置爲系統內存的 25%。

         2. work_mem(integer):

    PostgreSQL 在執行排序操作時,會根據 work_mem 的大小決定是否將一個大的結果集拆分爲幾個小的和 work_mem 差不多 大小的臨時文件。顯然拆分的結果是降低了排序的速度。因此增加 work_mem 有助於提高排序的速度。然而需要指出的是,如果系 統中同時存在多個排序操作,那麼每個操作在排序時使用的內存數量均爲 work_mem,因此在我們設置該值時需要注意這一問題。

         3. maintence_work_mem(integer):

    指定在維護性操作中使用的最大內存數,如 VACUUM、CREATE INDEX 和 ALTER TABLE ADD FOREIGN KEY 等,該配置的 缺省值爲 16MB。因爲每個會話在同一時刻只能執行一個該操作,所以使用的頻率不高,但是這些指令往往消耗較多的系統資源,因 此應該儘快讓這些指令快速執行完畢。 
PostgreSQL 學習手冊(角色和權限) 
    PostgreSQL 是通過角色來管理數據庫訪問權限的,我們可以將一個角色看成是一個數據庫用戶,或者一組數據庫用戶。角色可以 擁有數據庫對象,如表、索引,也可以把這些對象上的權限賦予其它角色,以控制哪些用戶對哪些對象擁有哪些權限。      
一、數據庫角色: 
 
    1. 創建角色:

    CREATE ROLE role_name;

         2. 刪除角色:

    DROP ROLE role_name;

         3. 查詢角色:

    檢查系統表 pg_role,如:

    SELECT usename FROM pg_role;  

  也可以在 psql 中執行\du 命令列出所有角色。      
二、角色屬性: 

     一個數據庫角色可以有一系列屬性,這些屬性定義他的權限,以及與客戶認證系統的交互。

    1. 登錄權限:

  只有具有 LOGIN 屬性的角色纔可以用於數據庫連接,因此我們可以將具有該屬性的角色視爲登錄用戶,創建方法有如下兩種:     CREATE ROLE name LOGIN PASSWORD '123456‘;

    CREATE USER name PASSWORD '123456';

         2. 超級用戶:

    數據庫的超級用戶擁有該數據庫的所有權限,爲了安全起見,我們最好使用非超級用戶完成我們的正常工作。和創建普通用戶不同, 創建超級用戶必須是以超級用戶的身份執行以下命令:

    CREATE ROLE name SUPERUSER;

         3. 創建數據庫:

    角色要想創建數據庫,必須明確賦予創建數據庫的屬性,見如下命令:

    CREATE ROLE name CREATEDB;

         4. 創建角色:

    一個角色要想創建更多角色,必須明確給予創建角色的屬性,見如下命令:

    CREATE ROLE name CREATEROLE;      
三、權限: 
 
    數據庫對象在被創建時都會被賦予一個所有者,通常而言,所有者就是執行對象創建語句的角色。對於大多數類型的對象,其初始 狀態是隻有所有者(或超級用戶)可以對該對象做任何事情。如果要允許其它用戶可以使用該對象,必須賦予適當的權限。PostgreSQL 中預定義了許多不同類型的內置權限,如:SELECT、INSERT、UPDATE、DELETE、RULE、REFERENCES、TRIGGER、 CREATE、TEMPORARY、EXECUTE 和 USAGE。

    我們可以使用 GRANT 命令來賦予權限,如:

    GRANT UPDATE ON accounts TO joe;  

  對於上面的命令,其含義爲將 accounts 表的 update 權限賦予 joe 角色。此外,我們也可以用特殊的名字 PUBLIC 把對象的權 限賦予系統中的所有角色。在權限聲明的位置上寫 ALL,表示把適用於該對象的所有權限都賦予目標角色。

    要撤銷權限,使用合適的 REVOKE 命令:

    REVOKE ALL ON accounts FROM PUBLIC;

    其含義爲:對所有角色(PUBLIC)撤銷在 accounts 對象上的所有權限(ALL)。 
 
四、角色成員: 
 
    在系統的用戶管理中,通常會把多個用戶賦予一個組,這樣在設置權限時只需給該組設置即可,撤銷權限時也是從該組撤消。在 PostgreSQL 中,首先需要創建一個代表組的角色,之後再將該角色的 membership 權限賦給獨立的用戶角色即可。

    1. 創建一個組角色,通常而言,該角色不應該具有 LOGIN 屬性,如:

    CREATE ROLE name;

        2. 使用 GRANT 和 REVOKE 命令添加和撤消權限:

    GRANT group_role TO role1, ... ;

    REVOKE group_role FROM role1, ... ;

  一個角色成員可以通過兩種方法使用組角色的權限,如:

    1. 每個組成員都可以用 SET ROLE 命令將自己臨時"變成"該組成員,此後再創建的任何對象的所有者將屬於該組,而不是原有的 登錄用戶。

    2. 擁有 INHERIT 屬性的角色成員自動繼承它們所屬角色的權限。 www.linuxidc.com
    見如下示例:

    CREATE ROLE joe LOGIN INHERIT;  --INHERIT
是缺省屬性。
 
    CREATE ROLE admin NOINHERIT;

    CREATE ROLE wheel NOINHERIT;

    GRANT admin TO joe;

    GRANT wheel TO admin;

    現在我們以角色 joe 的身份與數據庫建立連接,那麼該數據庫會話將同時擁有角色 joe 和角色 admin 的權限,這是因爲 joe"繼承 (INHERIT)"了 admin 的權限。然而與此不同的是,賦予 wheel 角色的權限在該會話中將不可用,因爲 joe 角色只是 wheel 角色的 一個間接成員,它是通過 admin 角色間接傳遞過來的,而 admin 角色卻含有 NOINHERIT 屬性,這樣 wheel 角色的權限將無法被 joe 繼承。

  這樣 wheel 角色的權限將無法被 joe 繼承。此時,我們可以在該會話中執行下面的命令:

    SET ROLE admin;  

  在執行之後,該會話將只擁有 admin 角色的權限,而不再包括賦予 joe 角色的權限。同樣,在執行下面的命令之後,該會話只能 使用賦予 wheel 的權限。

    SET ROLE wheel;

    在執行一段時間之後,如果仍然希望將該會話恢復爲原有權限,可以使用下列恢復方式之一:

    SET ROLE joe;

    SET ROLE NONE;

    RESET ROLE;

    注意: SET ROLE 命令總是允許選取當前登錄角色的直接或間接組角色。因此,在變爲 wheel 之前沒必要先變成 admin。      角色屬性 LOGIN、SUPERUSER 和 CREATEROLE 被視爲特殊權限,它們不會像其它數據庫對象的普通權限那樣被繼承。如 果需要,必須在調用 SET ROLE 時顯示指定擁有該屬性的角色。比如,我們也可以給 admin 角色賦予 CREATEDB 和 CREATEROLE 權限,然後再以 joe 的角色連接數據庫,此時該會話不會立即擁有這些特殊權限,只有當執行 SET ROLE admin 命令之後當前會話 才具有這些權限。

     要刪除一個組角色,執行 DROP ROLE group_role 命令即可。然而在刪除該組角色之後,它與其成員角色之間的關係將被立 即撤銷(成員角色本身不會受影響)。不過需要注意的是,在刪除之前,任何屬於該組角色的對象都必須先被刪除或者將對象的所有者 賦予其它角色,與此同時,任何賦予該組角色的權限也都必須被撤消。 

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