• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            woaidongmao

            文章均收錄自他人博客,但不喜標題前加-[轉貼],因其丑陋,見諒!~
            隨筆 - 1469, 文章 - 0, 評論 - 661, 引用 - 0
            數據加載中……

            SQLite的查詢優化

              SQLite是個典型的嵌入式DBMS,它有很多優點,它是輕量級的,在編譯之后很小,其中一個原因就是在查詢優化方面比較簡單,它只是運用索引機制來進行優化的,經過對SQLite的查詢優化的分析以及對源代碼的研究,我將SQLite的查詢優總結如下:

            一、影響查詢性能的因素:

            1 對表中行的檢索數目,越小越好

            2 排序與否。

            3 是否要對一個索引。

            4 查詢語句的形式

            二、幾個查詢優化的轉換

            1 對于單個表的單個列而言,如果都有形如T.C=expr這樣的子句,并且都是用OR操作符連接起來,形如: x = expr1 OR expr2 = x OR x = expr3 此時由于對于OR,在SQLite中不能利用索引來優化,所以可以將它轉換成帶有IN操作符的子句:x IN(expr1,expr2,expr3)這樣就可以用索引進行優化,效果很明顯,但是如果在都沒有索引的情況下OR語句執行效率會稍優于IN語句的效率。

            2 如果一個子句的操作符是BETWEEN,在SQLite中同樣不能用索引進行優化,所以也要進行相應的等價轉換: 如:a BETWEEN b AND c可以轉換成:(a BETWEEN b AND c) AND (a>=b) AND (a<=c)。 在上面這個子句中, (a>=b) AND (a<=c)將被設為dynamic且是(a BETWEEN b AND c)的子句,那么如果BETWEEN語句已經編碼,那么子句就忽略不計,如果存在可利用的index使得子句已經滿足條件,那么父句則被忽略。

            3 如果一個單元的操作符是LIKE,那么將做下面的轉換:x LIKE abc%’,轉換成:x>=abc AND x<abd’。因為在SQLite中的LIKE是不能用索引進行優化的,所以如果存在索引的話,則轉換后和不轉換相差很遠,因為對LIKE不起作用,但如果不存在索引,那么LIKE在效率方面也還是比不上轉換后的效率的。

            三、 幾種查詢語句的處理(復合查詢)
            1
            .查詢語句為:<SelectA> <operator> <selectB> ORDER BY <orderbylist> ORDER BY
                
            執行方法: is one of UNION ALL, UNION, EXCEPT, or INTERSECT. 這個語句的執行過程是先將selectAselectB執行并且排序,再對兩個結果掃描處理,對上面四種操作是不同的,將執行過程分成七個子過程:

               outA: selectA的結果的一行放到最終結果集中

               outB: selectA的結果的一行放到最終結果集中(只有UNION操作和UNION ALL操作,其它操作都不放入最終結果集中)

                AltB: selectA的當前記錄小于selectB的當前記錄

                AeqB: selectA的當前記錄等于selectB的當前記錄

                AgtB: selectA的當前記錄大于selectB的當前記錄

                EofA: selectA的結果遍歷完

                EofB: selectB的結果遍歷完

                     下面就是四種操作的執行過程:

             執行順序

            UNION ALL

            UNION

            EXCEPT

            INTERSECT

            AltB:

            outA, nextA

            outA, nextA

            outA,nextA

            nextA

            AeqB:

            outA, nextA

            nextA

            nextA

            outA, nextA

            AgtB:

            outB, nextB

            outB, nextB

            nextB

            nextB

            EofA:

            outB, nextB

            outB, nextB

            halt

            halt

            EofB:

            outA, nextA

            outA, nextA

            outA,nextA

            halt

                 2 如果可能的話,可以把一個用到GROUP BY查詢的語句轉換成DISTINCT語句來查詢,因為GROUP BY有時候可能會用到index,而對于DISTINCT都不會用到索引的 。

            四、子查詢扁平化 

                 例子:SELECT a FROM (SELECT x+y AS a FROM t1 WHERE z<100) WHERE a>5

                 對這個SQL語句的執行一般默認的方法就是先執行內查詢,把結果放到一個臨時表中,再對這個表進行外部查詢,這就要對數據處理兩次,另外這個臨時表沒有索引,所以對外部查詢就不能進行優化了,如果對上面的SQL進行處理后可以得到如下SQL語句:SELECT x+y AS a FROM t1 WHERE z<100 AND a>5,這個結果顯然和上面的一樣,但此時只需要對

            數據進行查詢一次就夠了,另外如果在表t1上有索引的話就避免了遍歷整個表。

            運用flatten方法優化SQL的條件:

            1.子查詢和外查詢沒有都用集函數

            2.子查詢沒有用集函數或者外查詢不是個表的連接

            3.子查詢不是一個左外連接的右操作數

            4.子查詢沒有用DISTINCT或者外查詢不是個表的連接

            5.子查詢沒有用DISTINCT或者外查詢沒有用集函數

            6.子查詢沒有用集函數或者外查詢沒有用關鍵字DISTINCT

            7.子查詢有一個FROM語句

            8.子查詢沒有用LIMIT或者外查詢不是表的連接

            9.子查詢沒有用LIMIT或者外查詢沒有用集函數

            10.子查詢沒有用集函數或者外查詢沒用LIMIT

            11.子查詢和外查詢不是同時是ORDER BY子句

            12.子查詢和外查詢沒有都用LIMIT

            13.子查詢沒有用OFFSET

            14.外查詢不是一個復合查詢的一部分或者子查詢沒有同時用關鍵字ORDER BYLIMIT

            15.外查詢沒有用集函數子查詢不包含ORDER BY

            16.復合子查詢的扁平化:子查詢不是一個復合查詢,或者他是一個UNION ALL復合查詢,但他是都由若干個非集函數的查詢構成,他的父查詢不是一個復合查詢的子查詢,也沒有用集函數或者是DISTINCT查詢,并且在FROM語句中沒有其它的表或者子查詢,父查詢和子查詢可能會包含WHERE語句,這些都會受到上面111213條件的限制。

            例:   SELECT a+1 FROM (

                          SELECT x FROM tab

                          UNION ALL

                          SELECT y FROM tab

                           UNION ALL

                           SELECT abs(z*2) FROM tab2

                       ) WHERE a!=5 ORDER BY 1

            轉換為:

                    SELECT x+1 FROM tab WHERE x+1!=5

                       UNION ALL

                       SELECT y+1 FROM tab WHERE y+1!=5

                       UNION ALL

                       SELECT abs(z*2)+1 FROM tab2 WHERE abs(z*2)+1!=5

                       ORDER BY 1

            17.如果子查詢是一個復合查詢,那么父查詢的所有的ORDER BY語句必須是對子查詢的列的簡單引用

            18.子查詢沒有用LIMIT或者外查詢不具有WHERE語句

            子查詢扁平化是由專門一個函數實現的,函數為:

            static int flattenSubquery(

            Parse *pParse,       /* Parsing context */

            Select *p,           /* The parent or outer SELECT statement */

            int iFrom,           /* Index in p->pSrc->a[] of the inner subquery */

            int isAgg,           /* True if outer SELECT uses aggregate functions */

            int subqueryIsAgg    /* True if the subquery uses aggregate functions */

            )

                   它是在Select.c文件中實現的。顯然對于一個比較復雜的查詢,如果滿足上面的條件時對這個查詢語句進行扁平化處理后就可以實現對查詢的優化。如果正好存在索引的話效果會更好!

             

            五、連接查詢

                   在返回查詢結果之前,相關表的每行必須都已經連接起來,在SQLite中,這是用嵌套循環實現的,在早期版本中,最左邊的是最外層循環,最右邊的是最內層循環,連接兩個或者更多的表時,如果有索引則放到內層循環中,也就是放到FROM最后面,因為對于前面選中的每行,找后面與之對應的行時,如果有索引則會很快,如果沒有則要遍歷整個表,這樣效率就很低,但在新版本中,這個優化已經實現。

                   優化的方法如下:

                   對要查詢的每個表,統計這個表上的索引信息,首先將代價賦值為SQLITE_BIG_DBL(一個系統已經定義的常量):

            1)    如果沒有索引,則找有沒有在這個表上對rowid的查詢條件:

            1.如果有Rowid=EXPR,如果有的話則返回對這個表代價估計,代價計為零,查詢得到的記錄數為1,并完成對這個表的代價估計,

            2.如果沒有Rowid=EXPR 但有rowid IN (...),而IN是一個列表,那么記錄返回記錄數為IN列表中元素的個數,估計代價為NlogN,

            3.如果IN不是一個列表而是一個子查詢結果,那么由于具體這個子查詢不能確定,所以只能估計一個值,返回記錄數為100,代價為200

            4.如果對rowid是范圍的查詢,那么就估計所有符合條件的記錄是總記錄的三分之一,總記錄估計為1000000,并且估計代價也為記錄數。

            5.如果這個查詢還要求排序,則再另外加上排序的代價NlogN

            6.如果此時得到的代價小于總代價,那么就更新總代價,否則不更新。

            2)    如果WHERE子句中存在OR操作符,那么要把這些OR連接的所有子句分開再進行分析。

            1 如果有子句是由AND連接符構成,那么再把由AND連接的子句再分別分析。

            2 如果連接的子句的形式是X<op><expr>,那么就再分析這個子句。

            3 接下來就是把整個對OR操作的總代價計算出來。

            4 如果這個查詢要求排序,則再在上面總代價上再乘上排序代價NlogN

            5 如果此時得到的代價小于總代價,那么就更新總代價,否則不更新。

            3)    如果有索引,則統計每個表的索引信息,對于每個索引:

            1 先找到這個索引對應的列號,再找到對應的能用到(操作符必須為=或者是IN(…))這個索引的WHERE子句,如果沒有找到,則退出對每個索引的循環,如果找到,則判斷這個子句的操作符是什么,如果是=,那么沒有附加的代價,如果是INsub-select),那么估計它附加代價inMultiplier25,如果是INlist),那么附加代價就是NNlist的列數)。

            2 再計算總的代價和總的查詢結果記錄數和代價。

            3 nRow = pProbe->aiRowEst[i] * inMultiplier;/*計算行數*/

            4 cost = nRow * estLog(inMultiplier);/*統計代價*/

            5 如果找不到操作符為=或者是IN(…)的子句,而是范圍的查詢,那么同樣只好估計查詢結果記錄數為nRow/3,估計代價為cost/3

            6 同樣,如果此查詢要求排序的話,再在上面的總代價上加上NlogN

            7 如果此時得到的代價小于總代價,那么就更新總代價,否則不更新。

            4)    通過上面的優化過程,可以得到對一個表查詢的總代價(就是上面各個代價的總和),再對第二個表進行同樣的操作,這樣如此直到把FROM子句中所有的表都計算出各自的代價,最后取最小的,這將作為嵌套循環的最內層,依次可以得到整個嵌套循環的嵌套順序,此時正是最優的,達到了優化的目的。

            5)    所以循環的嵌套順序不一定是與FROM子句中的順序一致,因為在執行過程中會用索引優化來重新排列順序。

            六、索引

               SQLite中,有以下幾種索引:

            1)    單列索引

            2)    多列索引

            3)    唯一性索引

            4)    對于聲明為:INTEGER PRIMARY KEY的主鍵來說,這列會按默認方式排序,所以雖然在數據字典中沒有對它生成索引,但它的功能就像個索引。所以如果在這個主鍵上在單獨建立索引的話,這樣既浪費空間也沒有任何好處。

            運用索引的注意事項:

            1)    對于一個很小的表來說沒必要建立索引

            2)    在一個表上如果經常做的是插入更新操作,那么就要節制使用索引

            3)    也不要在一個表上建立太多的索引,如果建立太多的話那么在查詢的時候SQLite可能不會選擇最好的來執行查詢,一個解決辦法就是建立聚蔟索引

            索引的運用時機:

            1)    操作符:=><IN

            2)    操作符BETWEENLIKEOR不能用索引,

                   BETWEENSELECT * FROM mytable WHERE myfield BETWEEN 10 and 20;

                   這時就應該將其轉換成:

                   SELECT * FROM mytable WHERE myfield >= 10 AND myfield <= 20;

                   此時如果在myfield上有索引的話就可以用了,大大提高速度

             

                   再如LIKESELECT * FROM mytable WHERE myfield LIKE 'sql%';

                   此時應該將它轉換成:

                   SELECT * FROM mytable WHERE myfield >= 'sql' AND myfield < 'sqm';

                   此時如果在myfield上有索引的話就可以用了,大大提高速度

             

                   再如ORSELECT * FROM mytable WHERE myfield = 'abc' OR myfield = 'xyz';

                   此時應該將它轉換成:

                   SELECT * FROM mytable WHERE myfield IN ('abc', 'xyz');

                   此時如果在myfield上有索引的話就可以用了,大大提高速度

            3)    有些時候索引都是不能用的,這時就應該遍歷全表(程序演示)

                   SELECT * FROM mytable WHERE myfield % 2 = 1;

                   SELECT * FROM mytable WHERE substr(myfield, 0, 1) = 'w';

                   SELECT * FROM mytable WHERE length(myfield) < 5;

             

            上次講到了SQLite的查詢優化代碼中的具體實現,現在來看一下它的幾個實例:

              1 #include "stdio.h"
              2 #include "sqlite3.h"
              3 #include <windows.h>
              4 void query(sqlite3 *db,sqlite3_stmt *stmt,char * sql);
              5 
              6 int main(int argc, char **argv)
              7 {
              8     sqlite3 *db;
              9     char *zErr;
            10     int rc;
            11     char *sql;
            12     sqlite3_stmt *stmt=0;
            13     rc = sqlite3_open("memory.db", &db);
            14     if(rc) {
            15         fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
            16         sqlite3_close(db);
            17     }
            18 
            19     //
            下面是所建的各個表的結構
            20     sql="CREATE TABLE t1 (num int,word TEXT NOT NULL)";
            21     //sql="CREATE TABLE t4 (num INTEGER NOT NULL,word TEXT NOT NULL)";
            22     //sql="CREATE TABLE t3 (num INTEGER NOT NULL,word TEXT NOT NULL)";
            23     rc = sqlite3_exec(db, sql, NULL, NULL, &zErr);
            24     if(rc != SQLITE_OK) {
            25         if (zErr != NULL) {
            26             fprintf(stderr, "SQL error: %s\n", zErr);
            27             sqlite3_free(zErr);
            28         }
            29     }
            30 
            31     //
            下面是對所以插入進行手動提交,這樣可以加快插入速度
            32     //sqlite3_exec(db,"BEGIN",NULL,NULL,&zErr);
            33     //
            插入1000000條記錄
            34     //for (int i=0;i<1000000;i++)
            35     //{
            36     //    sql = sqlite3_mprintf("insert into t1 values(%d,'%s')",i,"goodc");
            37     //    rc = sqlite3_exec(db, sql, NULL, NULL, &zErr);
            38     //}
            39     //sqlite3_exec(db,"COMMIT",NULL,NULL,&zErr);
            40    
            41     sql="create index t1nwindex on t1(num)";
            42     rc=sqlite3_exec(db, sql, NULL, NULL, &zErr);
            43     if(rc != SQLITE_OK) {
            44         if (zErr != NULL) {
            45             fprintf(stderr, "SQL error: %s\n", zErr);
            46             sqlite3_free(zErr);
            47         }
            48     }
            49 
            50     //sql="drop index t1nwindex";
            51     //sql="drop index t3index";
            52     //sql="delete from t2";
            53     rc=sqlite3_exec(db, sql, NULL, NULL, &zErr);
            54     if(rc != SQLITE_OK) {
            55         if (zErr != NULL) {
            56             fprintf(stderr, "SQL error: %s\n", zErr);
            57             sqlite3_free(zErr);
            58         }
            59     }
            60 
            61     printf("
            查詢結果是:\n");
            62     //sql="select * from t1 where num=3000 or num=2000";//INTEGER PRIMARY KEY,快
            63     //sql="select * from t2 where num=3000 or num=2000";//沒有索引,慢
            64     //sql="select * from t3 where num=3000 or num=2000";//有索引,快
            65    
            66     //
            這里交換位置了,但是結果用的時間想差比較大的原因是,t1是用索引存儲的,但是它不是由create index
            67     //而創建的,所以系統還不會把它作為索引處理,所以這兩個表就只是無索引的表,在內部優化計算代價只是對它
            68     //進行估計,因為源代碼中沒有捕獲到下面的查詢條件,所以都是系統最大值(源代碼中有),所以就嵌套順序沒
            69     //變,所以出現下面的差異。
            70     //sql="SELECT count(*) FROM t3, t1 WHERE t1.num = t3.num";//比下面的快,由于內層少
            71     //sql="SELECT count(*) FROM t1, t3 WHERE t1.num = t3.num";//比上面的慢,由于內層多
            72 
            73     //
            下面這個已經內部實現優化,所以所用時間是相同的
            74     //sql="SELECT * FROM t2, t3 WHERE t2.num = t3.num";//有索引,稍快
            75     //sql="SELECT * FROM t3, t2 WHERE t2.num = t3.num";//同上,內部已經優化
            76 
            77     //sql="select * from t3 where num=8000";//
            有索引,快
            78     //sql="select * from t1 where num%2=0";//有索引,但不能用,很慢
            79     //sql="select * from t1 where num=8000";//沒有索引,慢
            80    
            81     //BETWEEN
            的轉換優化---內部已經實現優化,如果有索引的話快一點
            82     //sql="select count(*) from t2 where word between 'goodl' and 'goodm'";//BETWEEN
            83     //sql="select count(*) from t2 where word >='goodl' and word<'goodm'";//BETWEEN
            的轉換
            84 
            85     //LIKE
            的轉換優化---內部已經實現優化
            86     //sql="select count(*) from t2 where word like 'goodl%'";//有索引不起作用
            87     //sql="select count(*) from t2 where word >='goodl' and word <'goodm'";//如果有索引會更快
            88 
            89     //IN
            的轉換優化---內部沒有實現優化,但此時如果可以用索引的話就會很好
            90     //如果不用索引則在這里體現不出INOR優,而如果有索引則差別很明顯
            91     //sql="select count(*) from t2 where word in('goodllll','goodkkkk','goodaaaa')";
            92     //sql="select count(*) from t2 where word ='goodllll' or word ='goodkkkk' or word='goodaaaa'";
            93 
            94     int start=GetTickCount();
            95     query(db,stmt,sql);
            96     printf("the time has pass:%dms\n",GetTickCount()-start);
            97     sqlite3_close(db);
            98     return 0;   
            99 }
            100 
            101 void query(sqlite3 *db,sqlite3_stmt *stmt,char * sql){
            102     int rc,ncols,i;
            103     const char *tail;
            104     rc = sqlite3_prepare(db, sql, -1, &stmt, &tail);
            105     if(rc != SQLITE_OK) {
            106         fprintf(stderr, "SQL error: %s\n", sqlite3_errmsg(db));
            107     }
            108     rc = sqlite3_step(stmt);
            109     ncols = sqlite3_column_count(stmt);
            110     while(rc == SQLITE_ROW) {   
            111         for(i=0; i < ncols; i++) {
            112             fprintf(stderr, "'%s' ", sqlite3_column_text(stmt, i));
            113         }
            114         fprintf(stderr, "\n");
            115         rc = sqlite3_step(stmt);
            116     }
            117 }

            Feedback

            #1    回復  引用  查看    

            2009-03-03 13:53 by aierong     

            我也喜歡sqliteclip_image001

            #2    回復  引用  查看    

            2009-03-03 17:25 by 玉開     

            me too

            #3 [樓主]   回復  引用  查看    

            2009-03-03 17:29 by 上善     

            SQLite確實很吸引人啊,很典型的一個嵌入式DBMS,值得好好研究。

            #4    回復  引用    

            2009-03-03 23:50 by 木頭羊 [未注冊用戶]

            我的官方站點就是用這個數據庫的,速度挺快的

            #5    回復  引用    

            2009-03-04 08:29 by @木頭羊 [未注冊用戶]

            @木頭羊
            速度確實快啊,很方便用的;@木頭羊

            #6    回復  引用    

            2009-03-04 08:30 by king2009 [未注冊用戶]

            再如LIKESELECT * FROM mytable WHERE myfield LIKE 'sql%';

            此時應該將它轉換成:

            SELECT * FROM mytable WHERE myfield >= 'sql' AND myfield < 'sqm';

            這句什么意思啊?模糊查詢也能轉換成>= and < ?????????????

            #7    回復  引用    

            2009-03-04 10:05 by @上善 [未注冊用戶]

            @king2009

            如果查詢是LIKE 'sql%';,那么說明要查詢的字符串格式是前三位必須是sql,后面可以是任意位任何字符,那么 myfield >= 'sql' 的意思就是說明要找的字符串要比'sql'長或者就是'sql',而后面的'sqm'的意思是說要找的字符串的第三個必須要比m小,再加上前面的限制條件,所以只能是'l'了,所以myfield >= 'sql' AND myfield < 'sqm'就找到了LIKE 'sql%'的字符串。

            #8    回復  引用    

            2009-03-04 11:38 by king2009 [未注冊用戶]

            噢,如果是like 中文的話,那就是沒轍了!
            LIKE '
            張三%'
            myfield >= '
            張三' AND myfield < '???'

            #9 [樓主]   回復  引用  查看    

            2009-03-04 13:01 by 上善     

            你可真會想,對于中文的話要編碼的,看用哪種編碼方式了,如果是中文的話現在就不用索引而是用LIKE就行了。

             

            posted on 2009-06-20 03:10 肥仔 閱讀(4646) 評論(0)  編輯 收藏 引用 所屬分類: 數據庫

            久久人人爽人人爽人人片AV不| 狠狠色丁香久久综合五月| 思思久久99热免费精品6| 久久综合给合久久狠狠狠97色69 | 久久久久国产成人精品亚洲午夜| 亚洲国产精品成人久久蜜臀| 久久精品国产99久久久| 久久人人爽人人澡人人高潮AV| 色综合久久无码中文字幕| 国产精品99久久久久久猫咪| 亚洲精品乱码久久久久久自慰 | 一本色道久久88综合日韩精品| 色婷婷久久综合中文久久蜜桃av | 亚洲а∨天堂久久精品| 精品人妻久久久久久888| 中文字幕久久精品| 94久久国产乱子伦精品免费| 色欲久久久天天天综合网精品| 久久精品国产清自在天天线| 99麻豆久久久国产精品免费| 久久久久亚洲av成人网人人软件 | 国产精品欧美亚洲韩国日本久久 | 人人狠狠综合久久亚洲| 久久精品国产91久久麻豆自制| 国内精品综合久久久40p| 久久久久这里只有精品| 国产精品狼人久久久久影院| 久久美女网站免费| 人妻无码久久一区二区三区免费| 思思久久精品在热线热| 色综合久久夜色精品国产| 欧美与黑人午夜性猛交久久久| 91亚洲国产成人久久精品| 97久久精品人人澡人人爽| 精品国产福利久久久| 久久不射电影网| 大蕉久久伊人中文字幕| 国产日韩久久免费影院 | 国产精品久久亚洲不卡动漫| 久久精品国产亚洲AV嫖农村妇女 | 久久久九九有精品国产|