翻動(dòng)
100
萬級(jí)的數(shù)據(jù)?——?只需幾十毫秒?之揭秘篇:有詳細(xì)的說明,不要錯(cuò)過。
感謝大家的支持!!!
昨天發(fā)了一個(gè)邀請(qǐng),邀請(qǐng)大家?guī)兔y試,效果還可以,下面小結(jié)一下:
通過內(nèi)部的計(jì)數(shù)器得知:訪問次數(shù)是
1071
(其中有好多是自己點(diǎn)的:)),人數(shù)不是太理想,本來是想看看上萬人同時(shí)訪問的情況:)
系統(tǒng)資源的占用情況
內(nèi)存?——?很理想。
SQL
占用的內(nèi)存最大也沒有超過
65
M,一般是在
35
M左右;asp.net占用的內(nèi)存最大也沒有超過
40
M,一般是在
25
M左右。
CPU:
8
%左右,由于訪問次數(shù)不多,也不夠集中,所以這個(gè)數(shù)值也說明不了什么。自己連續(xù)點(diǎn)了n次下一頁,發(fā)現(xiàn)CPU的使用率飄高,達(dá)到了
50
%左右。
但是對(duì)于
100
萬的記錄,AMD?XP2000+?的CPU?幾十毫秒的放映速度,因該是可以接受的,甚至是很理想的吧。
畢竟服務(wù)器的CPU要比我的快很多吧,而且記錄也很難達(dá)到
100
萬吧。
結(jié)果還是很滿意的,但是美中不足的是,我想看一下海量訪問的情況下的效果,
希望大家再支持一下,多點(diǎn)幾下,謝謝了。呵呵
另外說明一下:前n頁可以在
60
毫秒內(nèi)完成,n應(yīng)該是大于
500
的,小于多少嘛還沒有測試。后n頁就比較慢了,需要
500
毫秒左右。
下面討論一下翻頁的技巧吧。
我沒有用游標(biāo)、臨時(shí)表、
not?in
、
in?
這些方法,并不是說他們的效率不高,而是我還沒有測試過。我只用了?top?,查了兩次表。
大家也可提供一些其他的方法,我來測試一下,看看在
100
萬條的情況下的效果。(請(qǐng)不要給在存儲(chǔ)過程里面組串的,看著實(shí)在是太費(fèi)勁了)
討論的前提是在海量數(shù)據(jù)的情況下,至少是在
10
萬以上的。如果是很少的數(shù)據(jù)呢,那怎么翻都可以了。也差不了多少。
1.
設(shè)置合理的索引
首先要做的是設(shè)置合理的索引,這個(gè)好像經(jīng)常被忽略,至少很少被談起。
注意:主鍵是索引的一種,而且是最快的一種。如果你都是把主鍵當(dāng)作排序字段的話,那么你已經(jīng)利用了索引。
不設(shè)置合理的索引的話,會(huì)導(dǎo)致查詢速度非常的慢,甚至?xí)斐沙瑫r(shí)。
這方面你可以做一個(gè)實(shí)驗(yàn):找一個(gè)表,填進(jìn)去
10
萬條記錄,假設(shè)有ID?、addedDate等字段,在查詢分析器里面執(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字段沒有索引)
你會(huì)發(fā)現(xiàn)速度很慢。
現(xiàn)在給addedDate?加一個(gè)非聚集索引,然后在執(zhí)行上面的查詢語句,速度也變得很快了。
可見索引神奇的效果!
這是翻動(dòng)百萬級(jí)記錄最基本的設(shè)置,具體到我的那個(gè)論壇的翻頁,我是設(shè)置了BoardID、?replyDate兩個(gè)字段作為聯(lián)合索引的。
因?yàn)槭且谕粋€(gè)討論組李翻頁,而且是按replyDate排序的。
2.
只返回需要的記錄
對(duì)于海量數(shù)據(jù),都讀出來做緩存,那是不可想象的(記錄少的話,也要看利用率,一般都是很浪費(fèi)的)。
所以呢如果一頁顯示
20
條的話名那就只都讀出來
20
條,這樣就很省內(nèi)存和時(shí)間。
注意:雖然ADO.NET里面有這個(gè)方法
SqlDataAdapter.Fill(DataSet1,startRecord,maxRecords,srcTable);
但是他還是要先從
SQL
里面把查詢語句的查出來的所有記錄都出來,然后在截取指定的記錄數(shù)。這對(duì)于
SQL
來說是一樣的,對(duì)于海量數(shù)據(jù)依然會(huì)很慢。
論壇里的首頁用的是
select?
top?
20?
*?
from?table?where?
boardID?=?
5?
order?by?
replyDate?
desc
這樣呢就只返回了
20
條記錄,再加上索引的功勞,速度是非常快的。
?
3.
盡量減少字段的長度
一個(gè)表可以建很多的字段,但是字段的總長度不能超過
8060
B,也就是說如果你建了一個(gè)
char
(
8060
)的字段,就不能在建其他的字段了。
我在第一次的測試中(星期天的),把主題的所有信息都放在了一個(gè)表里面,包括了一個(gè)nvarchar(
3600
)的主題內(nèi)容的字段,復(fù)制記錄的時(shí)候發(fā)現(xiàn)非常的慢,
當(dāng)達(dá)到
9
萬的時(shí)候,就已經(jīng)很慢的,勉強(qiáng)把記錄數(shù)拷貝到了
35
萬,加了索引,測試了一下,翻頁速度還是可以的,前n也都是很快的,后n頁就很慢了,
如果再加上查詢那就非常之慢了。
查看了一下數(shù)據(jù)文件嚇了一跳?——?他居然占用了
1.4
G的硬盤空間,怪不得拷貝和查詢都慢的要死呢。
于是修改了一下表結(jié)構(gòu),把那個(gè)nvarchar(
3600
)的主題內(nèi)容的字段踢了出去,放在一個(gè)單獨(dú)的表里面。
再重新拷貝記錄就非常的快了,很快就把記錄數(shù)從
16
表成了
1048577
。昨天的測試就是在這個(gè)條件下進(jìn)行的。
4.
技巧
終于到了翻頁算法的地方了,呵呵沒有等急吧。
思路呢就是先找到一個(gè)標(biāo)志,然后呢把大于(或小于)這個(gè)標(biāo)志的前n條記錄取出來。
什么?沒看懂。沒關(guān)系,我舉個(gè)例子吧。
假設(shè)是按ID倒序的,每一頁顯示
10
條記錄,有
100
條記錄,記錄號(hào)正好是
1
到
100
(怎么這么巧??為了說明方便嘛)
那么第一頁的記錄就是
100
到
91
、第二頁的記錄就是
90
到
81
、第三頁的記錄就是
80
到
71......
我現(xiàn)在要翻到第三頁,那么要找到第
21
行的記錄的ID的值(也就是
80
),然后把小于等于
80
的記錄用top?
10?
取出來就行了。
查詢語句
declare?
@pageSize?
int?
--返回一頁的記錄數(shù)
declare?
@CurPage?
int?
--頁號(hào)(第幾頁)1:第一頁;2:第二頁;......;-1最后一頁。
declare?
@Count?
int
declare?
@id?
int
set?
@pageSize=
10
set?
@CurPage?=
1
if?
@CurPage?=?-
1
begin
--最后一頁
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
這種方法,感覺上是很省內(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):無論翻到哪一頁,內(nèi)存的占用情況都不變,多人訪問內(nèi)存也不會(huì)不變,很多人呢,還沒有測試出來:)
缺點(diǎn):單表、單排序字段。
?
?
http://community.csdn.net/Expert/TopicView3.asp?id=
4182510
發(fā)了這個(gè)帖子,回復(fù)的人很多,感謝大家的支持。這里有個(gè)誤會(huì)我不得不說明一下,免的誤人子弟。
在帖子里我并不是寫了個(gè)算法就完事了,而是說了很多翻動(dòng)海量數(shù)據(jù)要注意的地方,
比如建立合理的索引,只返回需要的記錄?,盡量減少字段的長度?等注意到或沒有注意到的地方。
最后說的才是算法,可能是我的表達(dá)能力太差了吧,舉的例子給大家?guī)砹苏`會(huì)。
翻頁的語句?(?@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
類型的字段排序)
一頁顯示
20
條記錄,這是顯示第三頁的語句
@pageSize?*?(@CurPage?-
1
)?+?
1?
=?
20
*(
3-1
)?+?
1?
=?
41
正是因?yàn)镮D是不連續(xù)的所以才需要用第一個(gè)語句來定位,如果是連續(xù)的那還用第一條語句做什么呢?
舉各少量數(shù)據(jù)的例子:
假設(shè)有
10
條記錄,ID是:
1000
,
500
,
320
,
205
,
115
,
110
,
95
,
68
,
4
,
1
。這回不寫連續(xù)的了免的誤會(huì)
一頁顯示兩條記錄,現(xiàn)在要顯示第三頁,那么第三頁的id就是?
115
,
110
先看第一條語句
select?
top?
5?
@id=ID?
from?table?order?by?
ID?
desc
不知道大家有沒有看懂這句,這時(shí)print?@id?得到的結(jié)果是?
115
。
再看第二條語句
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.主鍵是最快的索引,可能你還沒有意識(shí)到(一開始我就不知道,學(xué)了
SQL
很久以后才知道的),如果你的算法用它作為排序字段,那么速度會(huì)很快,會(huì)比用其他字段(沒有索引的字段)排序快很多。
C.用ReplyDate(最后回復(fù)時(shí)間)來排序,那么就必須給他建立索引(在海量數(shù)據(jù)的情況下),否則會(huì)超時(shí)的。
D.建立索引后,再執(zhí)行添加、修改、刪除會(huì)對(duì)數(shù)據(jù)庫帶來災(zāi)難性的折磨??
一開始我也是這么認(rèn)為的,但是為了能夠翻頁,不得不加索引。
但是接下來的事實(shí)確打消了我的顧慮
先來看添加。
100
萬條記錄是怎么弄出來的?大家可以看到帖子里有很多標(biāo)題一樣的主題,對(duì)了是復(fù)制出來的。
我先加了
16
條記錄,然后加上了索引。注意在
insert?into?
之前就已經(jīng)建立好了索引!
接下來就是
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
完了。
最后一次也只不過一兩分鐘(具體的時(shí)間忘記了,反正是很快了)。
同時(shí),論壇也提供了發(fā)貼的功能,只是在批量添加記錄的時(shí)候,把一些記錄的最后回復(fù)時(shí)間弄成了
2006
年,
所以,你發(fā)的帖子不會(huì)顯示在第一頁。但是你可以看到,執(zhí)行時(shí)間是很快的。
可見添加的時(shí)候是不成問題的,索引是倒序排列的,所以影響的行數(shù)絕對(duì)沒有你想象的那么多。
再來看修改。
看了sp1234的回復(fù),加了修改的功能,只是為了測試,所以呢可以修改標(biāo)題、最后發(fā)表時(shí)間、分組ID。
為什么可以修改這幾個(gè)字段呢?標(biāo)題是普通字段,最后發(fā)表時(shí)間和分組ID是索引字段。
修改這幾個(gè)字段需要的時(shí)間都是很快的,在最后回復(fù)時(shí)間的右面有?[改]?[刪]?字樣,大家可以試一試。
同樣,修改的時(shí)候,影響的行數(shù)也不是很多。
最后看刪除
不多說了,論壇提供了這個(gè)功能,試一下就知道了。另外,刪除的時(shí)候,不用重新建立一遍索引吧?
在來說一下使用范圍吧。
首先呢這只是一種方法,而不是一個(gè)通用的存儲(chǔ)過程,也就是說要根據(jù)情況作適當(dāng)?shù)男薷摹?br />
最佳使用環(huán)境:
單表,單排序字段,可以利用索引。
注意事項(xiàng):
排序字段不必連續(xù),最好使用
int
、datetime類型的字段,字符串型的字段沒有試過,效果可能會(huì)略差。
表可以沒有主鍵,但是對(duì)于海量數(shù)據(jù)的情況下,必須建立合理的索引。
有一個(gè)比較致命的限制,大家好像都沒有發(fā)現(xiàn),那就是排序字段的重復(fù)性,
最好是沒有重復(fù)的,但不是說絕對(duì)不能有重復(fù)的記錄,有不要緊,只要不跨頁就行,跨頁的話就會(huì)擠掉若干條記錄,
用時(shí)間字段來排序,發(fā)生重復(fù)的記錄的可能性就很小了。
擴(kuò)展性:
bingbingcha(不思不歸,不孟不E,原來是頭大灰狼)?的回復(fù)很精彩
-----------------
這樣的技巧在
SQL
區(qū)都討論過了..速度是很快的..但是滿足不了需求的..實(shí)用性太差了..現(xiàn)在的企業(yè)需要用到分頁的大部分都是多表查詢..單表分頁滿足不了需求的..
這個(gè)存儲(chǔ)過程可以擴(kuò)展..用臨時(shí)表+樓主的方法..是個(gè)不錯(cuò)的選擇..
-----------------
對(duì)于多表關(guān)聯(lián)查詢,有兩種方法,第一種就是bingbingcha說的?——?“用臨時(shí)表+樓主的方法”,這是在海量數(shù)據(jù)的時(shí)候唯一可行的方法。
但是在小數(shù)據(jù)量的時(shí)候,這么些就有一點(diǎn)繁瑣,而且不容易歸納到通用的寫法里。
先來看一下查詢語句據(jù)的寫法:
聯(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
有沒有看到相同的地方:
select?
顯示的字段
from?
表
where?
條件
那么單表查詢和多表查詢有什么區(qū)別呢?
至少有很多的多表(單字段排序)查詢都是可用這種方式的。
注意:我并沒有說所有的多表(單字段排序)查詢都可以用,看具體情況了。
?
這是一個(gè)效率最高(需要合理的索引的幫忙),比較通用的翻頁方法。不知道這次我有沒有講明白。
==============================================================================
CREATE?PROCEDURE?
CN5135_SP_Pagination
/*
***************************************************************
**?千萬數(shù)量級(jí)分頁存儲(chǔ)過程?**
***************************************************************
參數(shù)說明:
1.Tables?:表名稱,視圖
2.PrimaryKey?:主關(guān)鍵字
3.Sort?:排序語句,不帶Order?By?比如:NewsID?Desc,OrderRows?Asc
4.CurrentPage?:當(dāng)前頁碼
5.PageSize?:分頁尺寸
6.Filter?:過濾語句,不帶Where
7.Group?:Group語句,不帶Group?By
效果演示:http://www.cn5135.com/_App/Enterprise/QueryResult.aspx
***************************************************************/
(
@Tables?
varchar
(
1000
),
@PrimaryKey?
varchar
(
100
),
@Sort?
varchar
(
200
)?=?
NULL
,
@CurrentPage?
int?
=?
1
,
@PageSize?
int?
=?
10
,
@Fields?
varchar
(
1000
)?=?
'*'
,
@Filter?
varchar
(
1000
)?=?
NULL
,
@
Group?varchar
(
1000
)?=?
NULL
)
AS
/*默認(rèn)排序*/
IF?
@Sort?
IS?NULL?OR?
@Sort?=?
''
SET?
@Sort?=?@PrimaryKey
DECLARE?
@SortTable?
varchar
(
100
)
DECLARE?
@SortName?
varchar
(
100
)
DECLARE?
@strSortColumn?
varchar
(
200
)
DECLARE?
@operator?
char
(
2
)
DECLARE?
@type?
varchar
(
100
)
DECLARE?
@prec?
int
/*設(shè)定排序語句.*/
IF?
CHARINDEX(
'DESC'
,@Sort)>
0
BEGIN
SET?
@strSortColumn?=?REPLACE(@Sort,?
'DESC'
,?
''
)
SET?
@operator?=?
'<='
END
ELSE
BEGIN
IF?
CHARINDEX(
'ASC'
,?@Sort)?=?
0
SET?
@strSortColumn?=?REPLACE(@Sort,?
'ASC'
,?
''
)
SET?
@operator?=?
'>='
END
IF?
CHARINDEX(
'.'
,?@strSortColumn)?>?
0
BEGIN
SET?
@SortTable?=?SUBSTRING(@strSortColumn,?
0
,?CHARINDEX(
'.'
,@strSortColumn))
SET?
@SortName?=?SUBSTRING(@strSortColumn,?CHARINDEX(
'.'
,@strSortColumn)?+?
1
,?LEN(@strSortColumn))
END
ELSE
BEGIN
SET?
@SortTable?=?@Tables
SET?
@SortName?=?@strSortColumn
END
SELECT?
@type=t.name,?@prec=c.prec
FROM?
sysobjects?o
JOIN?
syscolumns?c?
on?
o.id=c.id
JOIN?
systypes?t?
on?
c.xusertype=t.xusertype
WHERE?
o.name?=?@SortTable?
AND?
c.name?=?@SortName
IF?
CHARINDEX(
'char'
,?@type)?>?
0
SET?
@type?=?@type?+?
'('?
+?
CAST
(@prec?
AS?varchar
)?+?
')'
DECLARE?
@strPageSize?
varchar
(
50
)
DECLARE?
@strStartRow?
varchar
(
50
)
DECLARE?
@strFilter?
varchar
(
1000
)
DECLARE?
@strSimpleFilter?
varchar
(
1000
)
DECLARE?
@strGroup?
varchar
(
1000
)
/*默認(rèn)當(dāng)前頁*/
IF?
@CurrentPage?<?
1
SET?
@CurrentPage?=?
1
/*設(shè)置分頁參數(shù).*/
SET?
@strPageSize?=?
CAST
(@PageSize?
AS?varchar
(
50
))
SET?
@strStartRow?=?
CAST
(((@CurrentPage?-?
1
)*@PageSize?+?
1
)?
AS?varchar
(
50
))
/*篩選以及分組語句.*/
IF?
@Filter?
IS?NOT?NULL?AND?
@Filter?!=?
''
BEGIN
SET?
@strFilter?=?
'?WHERE?'?
+?@Filter?+?
'?'
SET?
@strSimpleFilter?=?
'?AND?'?
+?@Filter?+?
'?'
END
ELSE
BEGIN
SET?
@strSimpleFilter?=?
''
SET?
@strFilter?=?
''
END
IF?
@
Group?IS?NOT?NULL?AND?
@
Group?
!=?
''
SET?
@strGroup?=?
'?GROUP?BY?'?
+?@
Group?
+?
'?'
ELSE
SET?
@strGroup?=?
''
/*執(zhí)行查詢語句*/
EXEC
(
'
DECLARE?@SortColumn?'?
+?@type?+?
'
SET?ROWCOUNT?'?
+?@strStartRow?+?
'
SELECT?@SortColumn='?
+?@strSortColumn?+?
'?FROM?'?
+?@Tables?+?@strFilter?+?
'?'?
+?@strGroup?+?
'?ORDER?BY?'?
+?@Sort?+?
'
SET?ROWCOUNT?'?
+?@strPageSize?+?
'
SELECT?'?
+?@Fields?+?
'?FROM?'?
+?@Tables?+?
'?WHERE?'?
+?@strSortColumn?+?@operator?+?
'?@SortColumn?'?
+?@strSimpleFilter?+?
'?'?
+?@strGroup?+?
'?ORDER?BY?'?
+?@Sort?+?
'
'
)
GO
能在線給我說說不 QQ:75789214 Email:singleyw@qq.com