C# SQL優化 及 Linq 分頁


   每次寫博客,第一句話都是這樣的:程序員很苦逼,除了會寫程序,還得會寫博客!當然,希望將來的一天,某位老板看到此博客,給你的程序員職工加點薪資吧!因為程序員的世界除了苦逼就是沉默。我眼中的程序員大多都不愛說話,默默承受着編程的巨大壓力,除了技術上的交流外,他們不願意也不擅長和別人交流,更不樂意任何人走進他們的內心!

   悟出來一個道理,在這兒分享給大家:學歷代表你的過去,能力代表你的現在,學習代表你的將來。我們都知道計算機技術發展日新月異,速度驚人的快,你我稍不留神,就會被慢慢淘汰!因此:每日不間斷的學習是避免被淘汰的不二法寶。

   最近真的是忙的不能再忙了,我一直這樣喃喃自語:把自己分成三個也不夠用的!還好,一期項目接近尾聲,雖說還有些沒完成的功能,但也不多了!索性抽點時間寫篇博客吧,也希望大家能喜歡這篇博客。

   在講Linq分頁之前,首先和大家探討下SQL,如下:

   1)SQL的優化(嘻嘻:因為:我負責公司項目的查詢模塊,連續做了一個多月的查詢及半月多的BUG修改,因此:對SQL的優化還是頗有感悟的,在此分享給大家,希望大家喜歡。謝謝、):

   1、要想寫出優美的SQL語句,就必須明白索引查詢和非索引查詢,SQL在執行的過程中,索引查詢的效率要大大高於非索引查詢,因此:我們在寫SQL語句的時候,應盡量避免非索引查詢。當然,實在避免不了的情況下,也應盡可能的優化自己的SQL語句。

   1.1、索引查詢:要想使用索引查詢,就必須首先弄清楚數據表中的索引字段,在此以PLSQL和SQLServer為例進行說明:如下(右鍵一張表,選擇編輯/設計)

   PLSQL查看索引字段:

   

   SQLServer查看索引字段:

   

   基本原則就是能通過索引字段進行查詢的,就通過索引字段進行查詢。

   索引查詢基本都是精確查詢,在使用過程中應避免使用Like、in、ont in、EXISTS、DisTinct等關鍵詞,在此關於索引查詢不作太多說明,大家只需把下面的非索引查詢搞明白就可以了。

   1.2、非索引查詢:

   上文中提高:要盡可能的使用索引字段進行查詢,那么,使用索引字段進行的查詢都能稱之為索引查詢嗎?

   答案是否定的,譬如:主鍵字段A是通過GUID生成的,A作為主鍵可能是索引字段,但是如果你針對A進行Like、in、not in 等操作就會造成非索引查詢。當然:一般沒有人針對GUID進行LIKE查詢,因此:此處舉的例子不怎么恰當,勿怪。

   那么在什么情況下會執行非索引查詢呢?我們應當避免使用哪些關鍵詞呢?如下:

   1、DisTinct 關鍵詞:使用此關鍵字會造成全表比對,慎用!

   2、Like 關鍵詞:LIKE操作符可以應用通配符查詢,里面的通配符組合可能達到幾乎是任意的查詢,但是如果用得不好則會產生性能上的問題,如LIKE ‘%5400%’ 這種查詢不會引用索引,而LIKE ‘X5400%’則會引用全范圍比對查找。

   3、in 關鍵詞和 not in 關鍵詞:用IN和NOT IN寫出來的SQL的優點是比較容易寫及清晰易懂,這比較適合現代軟件開發的風格。但是用IN的SQL性能總是比較低的,從Oracle執行的步驟來分析用IN的SQL與不用IN的SQL有以下區別:

ORACLE試圖將其轉換成多個表的連接,如果轉換不成功則先執行IN里面的子查詢,再查詢外層的表記錄,如果轉換成功則直接采用多個表的連接方式查詢。由此可見用IN的SQL至少多了一個轉換的過程。一般的SQL都可以轉換成功,但對於含有分組統計等方面的SQL就不能轉換了。

   話說另一個關鍵詞:Exists的執行效率要比in高出很多,因此:在這兒建議大家如果能用Exists解決問題時,就嘗試用Exists去解決問題吧!

   4、IS NULL關鍵詞和IS NOT NULL關鍵詞:索引是不會索引空值的,因此會全局非索引查詢。性能低下。

   5、UNION關鍵詞:UNION在進行表鏈接后會篩選掉重復的記錄,所以在表鏈接后會對所產生的結果集進行排序運算,刪除重復的記錄再返回結果。實際大部分應用中是不會產生重復的記錄,最常見的是過程表與歷史表UNION。如:

select * from gc_dfys
union
select * from ls_jg_dfys
這個SQL在運行時先取出兩個表的結果,再用排序空間進行排序刪除重復的記錄,最后返回結果集,如果表數據量大的話可能會導致用磁盤進行排序。

推薦方案:采用UNION ALL操作符替代UNION,因為UNION ALL操作只是簡單的將兩個結果合並后就返回。

select * from gc_dfys
union all
select * from ls_jg_dfys

   6、>= 及 <= 及 != 關鍵詞:>= 和 <=作用於索引字段時會采取索引查詢,但是如果作用於非索引字段,就會全表比對查詢,因此,要根據情況而定。

   7、Order by  group by 關鍵詞:order by  group by 談不上屬於非索引關鍵詞的范疇,但是使用order by  group by 時,排序/分組的字段要使用索引字段,否則效率大大降低

   以上是非索引查詢涉及到的關鍵詞,請大家慎重使用;

   8、慎重 or 關鍵字,即使用了,也應該結合索引字段進行查詢

   2)SQL的書寫規則:

   1、同一功能同一性能不同寫法SQL的影響。

   如一個SQL在A程序員寫的為  Select * from zl_yhjbqk

   B程序員寫的為 Select * from dlyx.zl_yhjbqk(帶表所有者的前綴)

   C程序員寫的為 Select * from DLYX.ZLYHJBQK(大寫表名)

   D程序員寫的為 Select *  from DLYX.ZLYHJBQK(中間多了空格)

   以上四個SQL在ORACLE分析整理之后產生的結果及執行的時間是一樣的,但是從ORACLE共享內存SGA的原理,可以得出ORACLE對每個SQL 都會對其進行一次分析,並且占用共享內存,如果將SQL的字符串及格式寫得完全相同,則ORACLE只會分析一次,共享內存也只會留下一次的分析結果,這不僅可以減少分析SQL的時間,而且可以減少共享內存重復的信息,ORACLE也可以准確統計SQL的執行頻率。

   2、WHERE后面的條件順序影響

   SQL1、select * from A where A.Name like '%陳%'

   SQL2、select * from A where A.Id>30 and A.id<=50 and A.Name like '%陳%' 

   以上兩個SQL中,第一個會全局查找,第二個會先執行索引查詢(將范圍鎖定為20條記錄),然后在執行非索引查詢。因此:SQL1的性能會大大低於SQL2的性能。

   3、SQL的執行順序是先執行子查詢,在逐步執行外層的查詢,因此:在書寫SQL的過程中,應盡可能的針對子查詢進行精確查詢且應盡可能的縮小子查詢的結果集、

   4、如果您僅僅只需要查詢三個字段,請勿查詢所有字段,譬如:select A.Id ,A.Name ,A.Sex from A 和 Select * from A 執行效率絕對是不一樣滴。

   5、統計查詢時:譬如:select Count(1) from A 和 Select Count(*) from A 執行效率絕對是不一樣滴。Count(1)的效率要遠遠大於Count(*)

   6、能使用視圖的話,就盡可能使用視圖,如果你將A,B,C三張表進行連接查詢,那么A,B,C三張表將會進行一個笛卡爾積連接,這個過程會大大增加系統的開銷,因此:連接查詢效率也不會很高。如果做成了視圖,就相當於數據庫事先做好了三張表的笛卡爾積,這樣就省去了這一步驟,因此效率會增加!

   7、針對時間字段:如果數據庫中時間字段定義成varchar或nvarchar類型,請盡可能的進行數據類型轉換,然后使用between and 進行查詢,因為between and 執行的是索引查詢。

   8、批量插入數據時應盡可能的一次執行而非一次一次執行:例如: 插入一千條數據:

  程序員A:

for(int i=0;i<1000;i++){

insert into A values(....)

連接數據庫,執行插入操作,單獨執行1000次插入

}

程序員B:

StringBuilder sb = new StringBuilder();

for(int i=0;i<1000;i++){

sb.Append("insert into A values(....);");

}

連接數據庫,執行插入操作(將所有的插入語句作為一個整體)

同樣是插入一千條數據,程序員B的效率要大大高於程序員A

   除了以上的敘述外,我們在設計數據庫的時候,也應做到最好,譬如:盡量不用text等類型作為字段類型,關於數據庫設計的優化,大家自行學習吧!

   講了這么多,上述都是鋪墊,還沒有涉及到Linq分頁,下面開講

   咱繼續哈:話說領導昨天找我談話了,說我做的部分查詢模塊效率不錯,但是沒能做到實時刷新,在這兒透漏給大家一個消息,我用的就是Linq查詢和分頁,效率高了,但又達不到客戶的需求,哎...等着被炒魷魚吧,領導就是這樣,只看結果,不管你怎么實現的。

   嘿嘿,在這兒也要提醒大家,用Linq做的查詢和分頁,是不和數據庫交互的,因此做不到實時刷新的哈。實現的基本方式就是:在數據庫中進行查詢,把查詢的結果轉化為一個大的集合,然后通過LINQ操作這個集合,所謂的操作也就是查詢和分頁。因此,你的查詢模塊和分頁模塊都是通過LINQ操作咱們首次查出來的大集合實現的,所以:你做的查詢和分頁就不會和數據庫進行交互了,因此做不到實時刷新。

   咱繼續吹哈,話說,我今天裝一把B,給大家演示一個LINQ查詢和分頁的案列,實現方式就通過C#控制台應用程序吧。

   示例代碼如下(通過lambda表達式進行相關查詢):

namespace LINQ
{
    class Program
    {
        static void Main(string[] args)
        {
            List<Preson> Lst = LoadData();
            foreach (var P in Lst)
            {
                Console.WriteLine(P.UserName + "&&" + P.Sex + "&&" + P.Age);
            }
            Console.Read();
        }

        /// <summary>
        /// 初始化10000條數據
        /// </summary>
        /// <returns></returns>
        public static List<Preson> LoadData()
        {
            List<Preson> result = new List<Preson>();
            for (int i = 0; i < 10000; i++)
            {
                Preson P = new Preson()
                {
                    UserName = "名字" + i,
                    Sex = (i % 2) == 0 ? "" : "",
                    Age = i % 100
                };
                result.Add(P);
            }
            return result;
        }
    }

    class Preson
    {
        private string userName;

        public string UserName
        {
            get { return userName; }
            set { userName = value; }
        }
        private string sex;

        public string Sex
        {
            get { return sex; }
            set { sex = value; }
        }
        private int age;

        public int Age
        {
            get { return age; }
            set { age = value; }
        }
    }
}

 

   這樣就生成了年齡0~9999歲的男人、女人各5000個,如下

   

    我們提出如下需求:

    1、根據人的年齡由大到小排序(類似於SQL語句中的Order BY DESC  、 ORDER BY ASC 關鍵字)

 static void Main(string[] args)
        {
            List<Preson> Lst = LoadData();
            Lst = Lst.OrderBy(p => p.Age).Reverse().ToList();
            foreach (var P in Lst)
            {
                Console.WriteLine("姓名:"+P.UserName + "   性別:" + P.Sex + "   年齡:" + P.Age);
            }
            Console.Read();
        }

 

   2、基於上述條件、查詢年齡介於9000至91000之間的人(類似於SQL語句> < =關鍵字)

 static void Main(string[] args)
        {
            List<Preson> Lst = LoadData();
            Lst = Lst.OrderBy(p => p.Age).Reverse().ToList();
            Lst = Lst.Where(p => p.Age >= 9000).Reverse().ToList();
            Lst = Lst.Where(p => p.Age <= 9100).Reverse().ToList();
            foreach (var P in Lst)
            {
                Console.WriteLine("姓名:"+P.UserName + "   性別:" + P.Sex + "   年齡:" + P.Age);
            }
            Console.Read();
        }

   3、基於上述條件、查詢姓名中帶有‘龍’的人(類似於SQL語句Like關鍵字中的左右%)

static void Main(string[] args)
        {
            List<Preson> Lst = LoadData();
            Lst = Lst.OrderBy(p => p.Age).Reverse().ToList();
            Lst = Lst.Where(p => p.Age >= 9000).Reverse().ToList();
            Lst = Lst.Where(p => p.Age <= 9100).Reverse().ToList();
            Lst = Lst.Where(p => p.UserName.Contains("")).ToList();
            foreach (var P in Lst)
            {
                Console.WriteLine("姓名:"+P.UserName + "   性別:" + P.Sex + "   年齡:" + P.Age);
            }
            Console.Read();
        }

   4、基於上述條件、查詢姓名以‘天才’開通的人(類似於SQL語句Like關鍵字中的左%)

static void Main(string[] args)
        {
            List<Preson> Lst = LoadData();
            Lst = Lst.OrderBy(p => p.Age).Reverse().ToList();
            Lst = Lst.Where(p => p.Age >= 9000).Reverse().ToList();
            Lst = Lst.Where(p => p.Age <= 9100).Reverse().ToList();
            Lst = Lst.Where(p => p.UserName.Contains("")).ToList();
            Lst = Lst.Where(p => p.UserName.StartsWith("天才")).ToList();
            foreach (var P in Lst)
            {
                Console.WriteLine("姓名:"+P.UserName + "   性別:" + P.Sex + "   年齡:" + P.Age);
            }
            Console.Read();
        }

   5、基於上述條件、查詢姓名以'卧龍'結尾的人(類似於SQL語句Like關鍵字中的右%)

 static void Main(string[] args)
        {
            List<Preson> Lst = LoadData();
            Lst = Lst.OrderBy(p => p.Age).Reverse().ToList();
            Lst = Lst.Where(p => p.Age >= 9000).Reverse().ToList();
            Lst = Lst.Where(p => p.Age <= 9100).Reverse().ToList();
            Lst = Lst.Where(p => p.UserName.Contains("")).ToList();
            Lst = Lst.Where(p => p.UserName.StartsWith("天才")).ToList();
            Lst = Lst.Where(p => p.UserName.EndsWith("卧龍")).ToList();
            foreach (var P in Lst)
            {
                Console.WriteLine("姓名:"+P.UserName + "   性別:" + P.Sex + "   年齡:" + P.Age);
            }
            Console.Read();
        }

   6、基於上述條件、去除重復元素(類似於SQL語句中的Distinct 關鍵字)

 static void Main(string[] args)
        {
            List<Preson> Lst = LoadData();
            Lst = Lst.OrderBy(p => p.Age).Reverse().ToList();
            Lst = Lst.Where(p => p.Age >= 9000).Reverse().ToList();
            Lst = Lst.Where(p => p.Age <= 9100).Reverse().ToList();
            Lst = Lst.Where(p => p.UserName.Contains("")).ToList();
            Lst = Lst.Where(p => p.UserName.StartsWith("天才")).ToList();
            Lst = Lst.Where(p => p.UserName.EndsWith("卧龍")).ToList();
            Lst = Lst.Distinct().ToList();
            foreach (var P in Lst)
            {
                Console.WriteLine("姓名:"+P.UserName + "   性別:" + P.Sex + "   年齡:" + P.Age);
            }
            Console.Read();
        }

   7、查詢年齡總和及平均年齡

 static void Main(string[] args)
        {
            List<Preson> Lst = LoadData();
            Lst = Lst.OrderBy(p => p.Age).Reverse().ToList();
            Lst = Lst.Where(p => p.Age >= 9000).Reverse().ToList();
            Lst = Lst.Where(p => p.Age <= 9100).Reverse().ToList();
            Lst = Lst.Where(p => p.UserName.Contains("")).ToList();
            Lst = Lst.Where(p => p.UserName.StartsWith("天才")).ToList();
            Lst = Lst.Where(p => p.UserName.EndsWith("卧龍")).ToList();
            Lst = Lst.Distinct().ToList();
            int A = Lst.Sum(p => p.Age); //查詢年齡總和
            double B = Lst.Average(p => p.Age);//查詢平均年齡

            foreach (var P in Lst)
            {
                Console.WriteLine("姓名:"+P.UserName + "   性別:" + P.Sex + "   年齡:" + P.Age);
            }
            Console.Read();
        }

   上述示例代碼中是通過lambda表達式進行相關查詢、Lambda表達式簡單易用、通俗易懂!當然,我們也可以通過LINQ表達式來寫以上查詢案例。那么,通過LINQ我們應當怎么去寫呢?

   代碼如下:

   static void Main(string[] args)
        {
            List<Preson> Lst = LoadData();
            //--------------------LINQ查詢如下--------------------//
            var Result = from r in Lst
                         where r.Age>=9000 && r.Age<=9100 && r.UserName.Contains("") &&r.UserName.StartsWith("天才")&&r.UserName.EndsWith("卧龍") orderby r.Age descending
                         select r;
            var Lst_2 = new List<Preson>();
            foreach (var A in Result)//需要進行一次Foreach操作哦
            {
                Lst_2.Add(A);
            }
            Lst = Lst_2;
            foreach (var P in Lst)
            {
                Console.WriteLine("姓名:"+P.UserName + "   性別:" + P.Sex + "   年齡:" + P.Age);
            }
            Console.Read();
        }

   上述LINQ的查詢結果和Lambda表達式查詢的結果是一樣滴,以上便是Linq和Lambda的查詢。LoadData()方法制造的10000條數據可以看作SQL查詢得到的結果集。

   那么,LINQ實現分頁應該怎么寫呢?談到這兒,總算是談到了LINQ分頁,其實LINQ分頁只需要用好兩個關鍵字,一個叫做:Skip、另一個叫做:Take

   其中Take關鍵字微軟的定義為:

        //
        // 摘要:
        //     從序列的開頭返回指定數量的連續元素。
        //
        // 參數:
        //   source:
        //     要從其返回元素的序列。
        //
        //   count:
        //     要返回的元素數量。
        //
        // 類型參數:
        //   TSource:
        //     source 中的元素的類型。
        //
        // 返回結果:
        //     一個 System.Collections.Generic.IEnumerable<T>,包含輸入序列開頭的指定數量的元素。
        //
        // 異常:
        //   System.ArgumentNullException:
        //     source 為 null。
        public static IEnumerable<TSource> Take<TSource>(this IEnumerable<TSource> source, int count);

   其中Skip關鍵字微軟的定義為:

        //
        // 摘要:
        //     跳過序列中指定數量的元素,然后返回剩余的元素。
        //
        // 參數:
        //   source:
        //     一個 System.Collections.Generic.IEnumerable<T>,用於從中返回元素。
        //
        //   count:
        //     返回剩余元素前要跳過的元素數量。
        //
        // 類型參數:
        //   TSource:
        //     source 中的元素的類型。
        //
        // 返回結果:
        //     一個 System.Collections.Generic.IEnumerable<T>,包含輸入序列中指定索引后出現的元素。
        //
        // 異常:
        //   System.ArgumentNullException:
        //     source 為 null。
        public static IEnumerable<TSource> Skip<TSource>(this IEnumerable<TSource> source, int count);

   看到上述微軟給出的定義,我突然想到一個面試題,在此分享給大家,大家也看看他們之間是不是有類似之處,是不是非常雷同呢?

   寫出一條Sql語句:取出表A中第31到第40記錄(SQLServer,以自動增長的ID

作為主鍵,注意:ID可能不是連續的。

   上述斜線的面試題如果用LINQ中的SKIP和TAKE怎么實現呢?

   基於上述查詢結果,我們接着來查詢第31到第40記錄,示例代碼如下:

   

 static void Main(string[] args)
        {
            List<Preson> Lst = LoadData();
            //--------------------LINQ查詢如下--------------------//
            var Result = from r in Lst
                         where r.Age>=9000 && r.Age<=9100 && r.UserName.Contains("") &&r.UserName.StartsWith("天才")&&r.UserName.EndsWith("卧龍") orderby r.Age descending
                         select r;
            var Lst_2 = new List<Preson>();
            foreach (var A in Result)//需要進行一次Foreach操作哦
            {
                Lst_2.Add(A);
            }
            Lst = Lst_2;
            Lst = Lst.Skip(30).Take(10).ToList();
            foreach (var P in Lst)
            {
                Console.WriteLine("姓名:"+P.UserName + "   性別:" + P.Sex + "   年齡:" + P.Age);
            }
            Console.Read();
        }
Lst.Skip(30)就是跳過前30行記錄,Take(10)就是取10行記錄,也就是取出31至40之間的記錄。

在此我們作個假設:如果PageSize代表頁容量(假設每頁有十條數據)、PageIndex代表頁碼,上述面試題無非就是讓我們取出第4頁的數據。
其示例代碼如下:
 static void Main(string[] args)
        {
            List<Preson> Lst = LoadData();
            //--------------------LINQ查詢如下--------------------//
            var Result = from r in Lst
                         where r.Age>=9000 && r.Age<=9100 && r.UserName.Contains("") &&r.UserName.StartsWith("天才")&&r.UserName.EndsWith("卧龍") orderby r.Age descending
                         select r;
            var Lst_2 = new List<Preson>();
            foreach (var A in Result)//需要進行一次Foreach操作哦
            {
                Lst_2.Add(A);
            }
            Lst = Lst_2;
            //Lst = Lst.Skip(30).Take(10).ToList();
            int PageSize = 10;//頁容量10,每頁十條數據
            int PageIndex = 1;//假設頁碼是從1開始的
            //根據分析得知:我們現在要取第4頁的數據,所以PageIndex=4,作如下賦值
            PageIndex = 4;
            Lst = Lst.Skip((PageIndex-1)*PageSize).Take(PageSize).ToList();

            foreach (var P in Lst)
            {
                Console.WriteLine("姓名:"+P.UserName + "   性別:" + P.Sex + "   年齡:" + P.Age);
            }
            Console.Read();
        }

   上述代碼已經作了詳細注釋,在此不作解讀了!

   至此:C#SQL優化和LINQ查詢、分頁就講完了,感謝您的查閱,謝謝!

   我想說的是:我么應當根據需求的不同,靈活選擇SQL查詢、分頁還是LINQ查詢分頁。如果您的數據量很大,並且系統要求效率高,不需要實時刷新,那么您可以采用LINQ查詢。

   不管多牛逼的LINQ查詢,在有數據庫的前提下,其數據源都是通過SQL查詢得到的,我們之所以用LINQ查詢分頁,無非就是為了減少和數據庫之間的交互,減少了和數據庫之間的交互,性能自然會提升。

   怎么用,自己看着辦吧!嘿嘿。

   最后做一個總結:

        #region Linq 分頁
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="Source"></param>
        /// <param name="PageSize">頁容量</param>
        /// <param name="CurrentPageIndex">頁碼索引 0 代表 第一頁</param>
        /// <returns></returns>
        public static IQueryable<T> Pagination<T>(IOrderedQueryable<T> Source, int PageSize, int CurrentPageIndex)
        {
            return Source.Skip(CurrentPageIndex * PageSize).Take(PageSize);
        }
        #endregion

   @陳卧龍的博客


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM