关于海量数据的SQL查询优化.........
討論的前提是在海量數(shù)據(jù)的情況下,至少是在10萬(wàn)以上的。如果是很少的數(shù)據(jù)呢,那怎么翻都可以了。也差不了多少。
1.設(shè)置合理的索引
首先要做的是設(shè)置合理的索引,這個(gè)好像經(jīng)常被忽略,至少很少被談起。
注意:主鍵是索引的一種,而且是最快的一種。如果你都是把主鍵當(dāng)作排序字段的話,那么你已經(jīng)利用了索引。
不設(shè)置合理的索引的話,會(huì)導(dǎo)致查詢(xún)速度非常的慢,甚至?xí)斐沙瑫r(shí)。
這方面你可以做一個(gè)實(shí)驗(yàn):找一個(gè)表,填進(jìn)去10萬(wàn)條記錄,假設(shè)有ID 、addedDate等字段,在查詢(xún)分析器里面執(zhí)行一下
select top 10 * from table
應(yīng)該立刻就能出現(xiàn)結(jié)果。
然后再執(zhí)行 select top 10 * from table order by ID(這時(shí)ID字段是主鍵)
也是立刻就出現(xiàn)了結(jié)果。
然后再執(zhí)行 select top 10 * from table order by addedDate (這時(shí)addedDate字段沒(méi)有索引)
你會(huì)發(fā)現(xiàn)速度很慢。
現(xiàn)在給addedDate 加一個(gè)非聚集索引,然后在執(zhí)行上面的查詢(xún)語(yǔ)句,速度也變得很快了。
可見(jiàn)索引神奇的效果!
這是翻動(dòng)百萬(wàn)級(jí)記錄最基本的設(shè)置,具體到我的那個(gè)論壇的翻頁(yè),我是設(shè)置了BoardID、 replyDate兩個(gè)字段作為聯(lián)合索引的。
因?yàn)槭且谕粋€(gè)討論組李翻頁(yè),而且是按replyDate排序的。
2.只返回需要的記錄
對(duì)于海量數(shù)據(jù),都讀出來(lái)做緩存,那是不可想象的(記錄少的話,也要看利用率,一般都是很浪費(fèi)的)。
所以呢如果一頁(yè)顯示20條的話名那就只都讀出來(lái)20條,這樣就很省內(nèi)存和時(shí)間。
注意:雖然ADO.NET里面有這個(gè)方法
SqlDataAdapter.Fill(DataSet1,startRecord,maxRecords,srcTable);
但是他還是要先從SQL里面把查詢(xún)語(yǔ)句的查出來(lái)的所有記錄都出來(lái),然后在截取指定的記錄數(shù)。這對(duì)于SQL來(lái)說(shuō)是一樣的,對(duì)于海量數(shù)據(jù)依然會(huì)很慢。
論壇里的首頁(yè)用的是select top 20 * from table where boardID = 5 order by replyDate desc
這樣呢就只返回了20條記錄,再加上索引的功勞,速度是非常快的。
?3.盡量減少字段的長(zhǎng)度
一個(gè)表可以建很多的字段,但是字段的總長(zhǎng)度不能超過(guò)8060B,也就是說(shuō)如果你建了一個(gè)char(8060)的字段,就不能在建其他的字段了。
我在第一次的測(cè)試中(星期天的),把主題的所有信息都放在了一個(gè)表里面,包括了一個(gè)nvarchar(3600)的主題內(nèi)容的字段,復(fù)制記錄的時(shí)候發(fā)現(xiàn)非常的慢,
當(dāng)達(dá)到9萬(wàn)的時(shí)候,就已經(jīng)很慢的,勉強(qiáng)把記錄數(shù)拷貝到了35萬(wàn),加了索引,測(cè)試了一下,翻頁(yè)速度還是可以的,前n也都是很快的,后n頁(yè)就很慢了,
如果再加上查詢(xún)那就非常之慢了。
查看了一下數(shù)據(jù)文件嚇了一跳 —— 他居然占用了1.4G的硬盤(pán)空間,怪不得拷貝和查詢(xún)都慢的要死呢。
于是修改了一下表結(jié)構(gòu),把那個(gè)nvarchar(3600)的主題內(nèi)容的字段踢了出去,放在一個(gè)單獨(dú)的表里面。
再重新拷貝記錄就非常的快了,很快就把記錄數(shù)從16表成了1048577。昨天的測(cè)試就是在這個(gè)條件下進(jìn)行的。
4.技巧
終于到了翻頁(yè)算法的地方了,呵呵沒(méi)有等急吧。
思路呢就是先找到一個(gè)標(biāo)志,然后呢把大于(或小于)這個(gè)標(biāo)志的前n條記錄取出來(lái)。
什么?沒(méi)看懂。沒(méi)關(guān)系,我舉個(gè)例子吧。
假設(shè)是按ID倒序的,每一頁(yè)顯示10條記錄,有100條記錄,記錄號(hào)正好是1到100(怎么這么巧??為了說(shuō)明方便嘛)
那么第一頁(yè)的記錄就是100到91、第二頁(yè)的記錄就是90到81、第三頁(yè)的記錄就是80到71......
我現(xiàn)在要翻到第三頁(yè),那么要找到第21行的記錄的ID的值(也就是80),然后把小于等于80的記錄用top 10 取出來(lái)就行了。
查詢(xún)語(yǔ)句
declare @pageSize int --返回一頁(yè)的記錄數(shù)
declare @CurPage int --頁(yè)號(hào)(第幾頁(yè))1:第一頁(yè);2:第二頁(yè);......;-1最后一頁(yè)。
declare @Count int
declare @id int
set @pageSize=10
set @CurPage =1
if @CurPage = -1
begin
--最后一頁(yè)
set rowcount @pageSize
select @id=ID from table order by ID
end
--定位
if @CurPage > 0
begin
set @Count = @pageSize * (@CurPage -1) + 1
set rowcount @Count
select @id=ID from table order by ID desc
end
--返回記錄
set rowcount @pageSize
select * from table where ID <=@id order by ID desc
set rowcount 0
其中“定位”用了 select @id=ID from table order by ID desc
這種方法,感覺(jué)上是很省內(nèi)存的,因?yàn)橹挥涗浟艘粋€(gè)ID,
然后用 select * from table where ID <=@id order by ID desc
取得最終需要的記錄
set rowcount @pageSize 相當(dāng)于 top @pageSize 。
優(yōu)點(diǎn):無(wú)論翻到哪一頁(yè),內(nèi)存的占用情況都不變,多人訪問(wèn)內(nèi)存也不會(huì)不變,很多人呢,還沒(méi)有測(cè)試出來(lái):)
缺點(diǎn):單表、單排序字段。
比如建立合理的索引,只返回需要的記錄 ,盡量減少字段的長(zhǎng)度 等注意到或沒(méi)有注意到的地方。
最后說(shuō)的才是算法,可能是我的表達(dá)能力太差了吧,舉的例子給大家?guī)?lái)了誤會(huì)。
翻頁(yè)的語(yǔ)句 ( @pageSize * (@CurPage -1) + 1 )
--定位
declare @id int
select top 41 @id=ID from table order by ID desc
--顯示數(shù)據(jù)
select top 20 * from table where ID <=@id order by ID desc
按照ID倒序排列(也就是按照int類(lèi)型的字段排序)
一頁(yè)顯示20條記錄,這是顯示第三頁(yè)的語(yǔ)句
@pageSize * (@CurPage -1) + 1 = 20*(3-1) + 1 = 41
正是因?yàn)镮D是不連續(xù)的所以才需要用第一個(gè)語(yǔ)句來(lái)定位,如果是連續(xù)的那還用第一條語(yǔ)句做什么呢?
舉各少量數(shù)據(jù)的例子:
假設(shè)有10條記錄,ID是:1000,500,320,205,115,110,95,68,4,1。這回不寫(xiě)連續(xù)的了免的誤會(huì)
一頁(yè)顯示兩條記錄,現(xiàn)在要顯示第三頁(yè),那么第三頁(yè)的id就是 115,110
先看第一條語(yǔ)句
select top 5 @id=ID from table order by ID desc
不知道大家有沒(méi)有看懂這句,這時(shí)print @id 得到的結(jié)果是 115。
再看第二條語(yǔ)句
select top 2 * from table where ID <=115 order by ID desc
這時(shí)的記錄集就是 115,110,也就是我們所需要的記錄了。
注意:不需要連續(xù)的ID,也不局限只能按ID排序,你可以換成ReplyDate(最后回復(fù)時(shí)間)字段,
當(dāng)然了declare @id int 要改成 declare @id datetime
這里的ID 是主鍵,唯一標(biāo)識(shí)記錄的字段,它本身就是一種索引,而且是效率最高的索引。
A.唯一標(biāo)識(shí)記錄的字段的值怎么能隨意改動(dòng)呢,那不亂套了嗎?
B.主鍵是最快的索引,可能你還沒(méi)有意識(shí)到(一開(kāi)始我就不知道,學(xué)了SQL很久以后才知道的),如果你的算法用它作為排序字段,那么速度會(huì)很快,會(huì)比用其他字段(沒(méi)有索引的字段)排序快很多。
C.用ReplyDate(最后回復(fù)時(shí)間)來(lái)排序,那么就必須給他建立索引(在海量數(shù)據(jù)的情況下),否則會(huì)超時(shí)的。
D.建立索引后,再執(zhí)行添加、修改、刪除會(huì)對(duì)數(shù)據(jù)庫(kù)帶來(lái)災(zāi)難性的折磨??
一開(kāi)始我也是這么認(rèn)為的,但是為了能夠翻頁(yè),不得不加索引。
但是接下來(lái)的事實(shí)確打消了我的顧慮
先來(lái)看添加。
100萬(wàn)條記錄是怎么弄出來(lái)的?大家可以看到帖子里有很多標(biāo)題一樣的主題,對(duì)了是復(fù)制出來(lái)的。
我先加了16條記錄,然后加上了索引。注意在insert into 之前就已經(jīng)建立好了索引!
接下來(lái)就是insert into table (...) select ... from table 影響的行數(shù):
16、32、64、128、256、512、1024、2048、4096、8192、16384、32768、65536、
131072、262144、524288 很快記錄就達(dá)到了100完了。
最后一次也只不過(guò)一兩分鐘(具體的時(shí)間忘記了,反正是很快了)。
同時(shí),論壇也提供了發(fā)貼的功能,只是在批量添加記錄的時(shí)候,把一些記錄的最后回復(fù)時(shí)間弄成了2006年,
所以,你發(fā)的帖子不會(huì)顯示在第一頁(yè)。但是你可以看到,執(zhí)行時(shí)間是很快的。
可見(jiàn)添加的時(shí)候是不成問(wèn)題的,索引是倒序排列的,所以影響的行數(shù)絕對(duì)沒(méi)有你想象的那么多。
再來(lái)看修改。
看了sp1234的回復(fù),加了修改的功能,只是為了測(cè)試,所以呢可以修改標(biāo)題、最后發(fā)表時(shí)間、分組ID。
為什么可以修改這幾個(gè)字段呢?標(biāo)題是普通字段,最后發(fā)表時(shí)間和分組ID是索引字段。
修改這幾個(gè)字段需要的時(shí)間都是很快的,在最后回復(fù)時(shí)間的右面有 [改] [刪] 字樣,大家可以試一試。
同樣,修改的時(shí)候,影響的行數(shù)也不是很多。
不多說(shuō)了,論壇提供了這個(gè)功能,試一下就知道了。另外,刪除的時(shí)候,不用重新建立一遍索引吧?
在來(lái)說(shuō)一下使用范圍吧。
首先呢這只是一種方法,而不是一個(gè)通用的存儲(chǔ)過(guò)程,也就是說(shuō)要根據(jù)情況作適當(dāng)?shù)男薷摹?br />最佳使用環(huán)境:
單表,單排序字段,可以利用索引。
注意事項(xiàng):
排序字段不必連續(xù),最好使用int、datetime類(lèi)型的字段,字符串型的字段沒(méi)有試過(guò),效果可能會(huì)略差。
表可以沒(méi)有主鍵,但是對(duì)于海量數(shù)據(jù)的情況下,必須建立合理的索引。
有一個(gè)比較致命的限制,大家好像都沒(méi)有發(fā)現(xiàn),那就是排序字段的重復(fù)性,
最好是沒(méi)有重復(fù)的,但不是說(shuō)絕對(duì)不能有重復(fù)的記錄,有不要緊,只要不跨頁(yè)就行,跨頁(yè)的話就會(huì)擠掉若干條記錄,
用時(shí)間字段來(lái)排序,發(fā)生重復(fù)的記錄的可能性就很小了。
擴(kuò)展性:
bingbingcha(不思不歸,不孟不E,原來(lái)是頭大灰狼) 的回復(fù)很精彩
-----------------
這樣的技巧在SQL區(qū)都討論過(guò)了..速度是很快的..但是滿(mǎn)足不了需求的..實(shí)用性太差了..現(xiàn)在的企業(yè)需要用到分頁(yè)的大部分都是多表查詢(xún)..單表分頁(yè)滿(mǎn)足不了需求的..
這個(gè)存儲(chǔ)過(guò)程可以擴(kuò)展..用臨時(shí)表+樓主的方法..是個(gè)不錯(cuò)的選擇..
-----------------
對(duì)于多表關(guān)聯(lián)查詢(xún),有兩種方法,第一種就是bingbingcha說(shuō)的 —— “用臨時(shí)表+樓主的方法”,這是在海量數(shù)據(jù)的時(shí)候唯一可行的方法。
但是在小數(shù)據(jù)量的時(shí)候,這么些就有一點(diǎn)繁瑣,而且不容易歸納到通用的寫(xiě)法里。
先來(lái)看一下查詢(xún)語(yǔ)句據(jù)的寫(xiě)法:
聯(lián)合的
SELECT a.ReplyID, a.TopicID
FROM dbo.BBS_Reply a INNER JOIN
dbo.BBS_body b ON a.BodyID = b.bodyID
where a.ReplyID >10
單表的
SELECT ReplyID, TopicID
FROM dbo.BBS_Reply
where ReplyID >10
有沒(méi)有看到相同的地方:
select 顯示的字段
from 表
where 條件
那么單表查詢(xún)和多表查詢(xún)有什么區(qū)別呢?
至少有很多的多表(單字段排序)查詢(xún)都是可用這種方式的。
注意:我并沒(méi)有說(shuō)所有的多表(單字段排序)查詢(xún)都可以用,看具體情況了。
總結(jié)
以上是生活随笔為你收集整理的关于海量数据的SQL查询优化.........的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 【Apache Mina2.0开发之二】
- 下一篇: not in SQL语句转化为 not