#
海量數據庫的查詢優化及分頁算法方案
隨著“金盾工程”建設的逐步深入和公安信息化的高速發展,公安計算機應用系統被廣泛應用在各警種、各部門。與此同時,應用系統體系的核心、系統數據的存放地――數據庫也隨著實際應用而急劇膨脹,一些大規模的系統,如人口系統的數據甚至超過了1000萬條,可謂海量。那么,如何實現快速地從這些超大容量的數據庫中提取數據(查詢)、分析、統計以及提取數據后進行數據分頁已成為各地系統管理員和數據庫管理員亟待解決的難題。
在以下的文章中,我將以“辦公自動化”系統為例,探討如何在有著1000萬條數據的MS SQL SERVER數據庫中實現快速的數據提取和數據分頁。以下代碼說明了我們實例中數據庫的“紅頭文件”一表的部分數據結構:
CREATE TABLE [dbo].[TGongwen] ( --TGongwen是紅頭文件表名
[Gid] [int] IDENTITY (1, 1) NOT NULL ,
--本表的id號,也是主鍵
[title] [varchar] (80) COLLATE Chinese_PRC_CI_AS NULL ,
--紅頭文件的標題
[fariqi] [datetime] NULL ,
--發布日期
[neibuYonghu] [varchar] (70) COLLATE Chinese_PRC_CI_AS NULL ,
--發布用戶
[reader] [varchar] (900) COLLATE Chinese_PRC_CI_AS NULL ,
--需要瀏覽的用戶。每個用戶中間用分隔符“,”分開
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
下面,我們來往數據庫中添加1000萬條數據:
declare @i int
set @i=1
while @i<=250000
begin
insert into Tgongwen(fariqi,neibuyonghu,reader,title) values('2004-2-5','通信科','通信科,辦公室,王局長,劉局長,張局長,admin,刑偵支隊,特勤支隊,交巡警支隊,經偵支隊,戶政科,治安支隊,外事科','這是最先的25萬條記錄')
set @i=@i+1
end
GO
declare @i int
set @i=1
while @i<=250000
begin
insert into Tgongwen(fariqi,neibuyonghu,reader,title) values('2004-9-16','辦公室','辦公室,通信科,王局長,劉局長,張局長,admin,刑偵支隊,特勤支隊,交巡警支隊,經偵支隊,戶政科,外事科','這是中間的25萬條記錄')
set @i=@i+1
end
GO
declare @h int
set @h=1
while @h<=100
begin
declare @i int
set @i=2002
while @i<=2003
begin
declare @j int
set @j=0
while @j<50
begin
declare @k int
set @k=0
while @k<50
begin
insert into Tgongwen(fariqi,neibuyonghu,reader,title) values(cast(@i as varchar(4))+'-8-15 3:'+cast(@j as varchar(2))+':'+cast(@j as varchar(2)),'通信科','辦公室,通信科,王局長,劉局長,張局長,admin,刑偵支隊,特勤支隊,交巡警支隊,經偵支隊,戶政科,外事科','這是最后的50萬條記錄')
set @k=@k+1
end
set @j=@j+1
end
set @i=@i+1
end
set @h=@h+1
end
GO
declare @i int
set @i=1
while @i<=9000000
begin
insert into Tgongwen(fariqi,neibuyonghu,reader,title) values('2004-5-5','通信科','通信科,辦公室,王局長,劉局長,張局長,admin,刑偵支隊,特勤支隊,交巡警支隊,經偵支隊,戶政科,治安支隊,外事科','這是最后添加的900萬條記錄')
set @i=@i+1000000
end
GO
通過以上語句,我們創建了25萬條由通信科于2004年2月5日發布的記錄,25萬條由辦公室于2004年9月6日發布的記錄,2002年和2003年各100個2500條相同日期、不同分秒的由通信科發布的記錄(共50萬條),還有由通信科于2004年5月5日發布的900萬條記錄,合計1000萬條。
一、因情制宜,建立“適當”的索引
建立“適當”的索引是實現查詢優化的首要前提。
索引(index)是除表之外另一重要的、用戶定義的存儲在物理介質上的數據結構。當根據索引碼的值搜索數據時,索引提供了對數據的快速訪問。事實上,沒有索引,數據庫也能根據SELECT語句成功地檢索到結果,但隨著表變得越來越大,使用“適當”的索引的效果就越來越明顯。注意,在這句話中,我們用了“適當”這個詞,這是因為,如果使用索引時不認真考慮其實現過程,索引既可以提高也會破壞數據庫的工作性能。
(一)深入淺出理解索引結構
實際上,您可以把索引理解為一種特殊的目錄。微軟的SQL SERVER提供了兩種索引:聚集索引(clustered index,也稱聚類索引、簇集索引)和非聚集索引(nonclustered index,也稱非聚類索引、非簇集索引)。下面,我們舉例來說明一下聚集索引和非聚集索引的區別:
其實,我們的漢語字典的正文本身就是一個聚集索引。比如,我們要查“安”字,就會很自然地翻開字典的前幾頁,因為“安”的拼音是“an”,而按照拼音排序漢字的字典是以英文字母“a”開頭并以“z”結尾的,那么“安”字就自然地排在字典的前部。如果您翻完了所有以“a”開頭的部分仍然找不到這個字,那么就說明您的字典中沒有這個字;同樣的,如果查“張”字,那您也會將您的字典翻到最后部分,因為“張”的拼音是“zhang”。也就是說,字典的正文部分本身就是一個目錄,您不需要再去查其他目錄來找到您需要找的內容。
我們把這種正文內容本身就是一種按照一定規則排列的目錄稱為“聚集索引”。
如果您認識某個字,您可以快速地從自動中查到這個字。但您也可能會遇到您不認識的字,不知道它的發音,這時候,您就不能按照剛才的方法找到您要查的字,而需要去根據“偏旁部首”查到您要找的字,然后根據這個字后的頁碼直接翻到某頁來找到您要找的字。但您結合“部首目錄”和“檢字表”而查到的字的排序并不是真正的正文的排序方法,比如您查“張”字,我們可以看到在查部首之后的檢字表中“張”的頁碼是672頁,檢字表中“張”的上面是“馳”字,但頁碼卻是63頁,“張”的下面是“弩”字,頁面是390頁。很顯然,這些字并不是真正的分別位于“張”字的上下方,現在您看到的連續的“馳、張、弩”三字實際上就是他們在非聚集索引中的排序,是字典正文中的字在非聚集索引中的映射。我們可以通過這種方式來找到您所需要的字,但它需要兩個過程,先找到目錄中的結果,然后再翻到您所需要的頁碼。
我們把這種目錄純粹是目錄,正文純粹是正文的排序方式稱為“非聚集索引”。
通過以上例子,我們可以理解到什么是“聚集索引”和“非聚集索引”。
進一步引申一下,我們可以很容易的理解:每個表只能有一個聚集索引,因為目錄只能按照一種方法進行排序。
(二)何時使用聚集索引或非聚集索引
下面的表總結了何時使用聚集索引或非聚集索引(很重要)。
動作描述
使用聚集索引
使用非聚集索引
列經常被分組排序
應
應
返回某范圍內的數據
應
不應
一個或極少不同值
不應
不應
小數目的不同值
應
不應
大數目的不同值
不應
應
頻繁更新的列
不應
應
外鍵列
應
應
主鍵列
應
應
頻繁修改索引列
不應
應
事實上,我們可以通過前面聚集索引和非聚集索引的定義的例子來理解上表。如:返回某范圍內的數據一項。比如您的某個表有一個時間列,恰好您把聚合索引建立在了該列,這時您查詢2004年1月1日至2004年10月1日之間的全部數據時,這個速度就將是很快的,因為您的這本字典正文是按日期進行排序的,聚類索引只需要找到要檢索的所有數據中的開頭和結尾數據即可;而不像非聚集索引,必須先查到目錄中查到每一項數據對應的頁碼,然后再根據頁碼查到具體內容。
(三)結合實際,談索引使用的誤區
理論的目的是應用。雖然我們剛才列出了何時應使用聚集索引或非聚集索引,但在實踐中以上規則卻很容易被忽視或不能根據實際情況進行綜合分析。下面我們將根據在實踐中遇到的實際問題來談一下索引使用的誤區,以便于大家掌握索引建立的方法。
1、主鍵就是聚集索引
這種想法筆者認為是極端錯誤的,是對聚集索引的一種浪費。雖然SQL SERVER默認是在主鍵上建立聚集索引的。
通常,我們會在每個表中都建立一個ID列,以區分每條數據,并且這個ID列是自動增大的,步長一般為1。我們的這個辦公自動化的實例中的列Gid就是如此。此時,如果我們將這個列設為主鍵,SQL SERVER會將此列默認為聚集索引。這樣做有好處,就是可以讓您的數據在數據庫中按照ID進行物理排序,但筆者認為這樣做意義不大。
顯而易見,聚集索引的優勢是很明顯的,而每個表中只能有一個聚集索引的規則,這使得聚集索引變得更加珍貴。
從我們前面談到的聚集索引的定義我們可以看出,使用聚集索引的最大好處就是能夠根據查詢要求,迅速縮小查詢范圍,避免全表掃描。在實際應用中,因為ID號是自動生成的,我們并不知道每條記錄的ID號,所以我們很難在實踐中用ID號來進行查詢。這就使讓ID號這個主鍵作為聚集索引成為一種資源浪費。其次,讓每個ID號都不同的字段作為聚集索引也不符合“大數目的不同值情況下不應建立聚合索引”規則;當然,這種情況只是針對用戶經常修改記錄內容,特別是索引項的時候會負作用,但對于查詢速度并沒有影響。
在辦公自動化系統中,無論是系統首頁顯示的需要用戶簽收的文件、會議還是用戶進行文件查詢等任何情況下進行數據查詢都離不開字段的是“日期”還有用戶本身的“用戶名”。
通常,辦公自動化的首頁會顯示每個用戶尚未簽收的文件或會議。雖然我們的where語句可以僅僅限制當前用戶尚未簽收的情況,但如果您的系統已建立了很長時間,并且數據量很大,那么,每次每個用戶打開首頁的時候都進行一次全表掃描,這樣做意義是不大的,絕大多數的用戶1個月前的文件都已經瀏覽過了,這樣做只能徒增數據庫的開銷而已。事實上,我們完全可以讓用戶打開系統首頁時,數據庫僅僅查詢這個用戶近3個月來未閱覽的文件,通過“日期”這個字段來限制表掃描,提高查詢速度。如果您的辦公自動化系統已經建立的2年,那么您的首頁顯示速度理論上將是原來速度8倍,甚至更快。
在這里之所以提到“理論上”三字,是因為如果您的聚集索引還是盲目地建在ID這個主鍵上時,您的查詢速度是沒有這么高的,即使您在“日期”這個字段上建立的索引(非聚合索引)。下面我們就來看一下在1000萬條數據量的情況下各種查詢的速度表現(3個月內的數據為25萬條):
(1)僅在主鍵上建立聚集索引,并且不劃分時間段:
Select gid,fariqi,neibuyonghu,title from tgongwen
用時:128470毫秒(即:128秒)
(2)在主鍵上建立聚集索引,在fariq上建立非聚集索引:
select gid,fariqi,neibuyonghu,title from Tgongwen
where fariqi> dateadd(day,-90,getdate())
用時:53763毫秒(54秒)
(3)將聚合索引建立在日期列(fariqi)上:
select gid,fariqi,neibuyonghu,title from Tgongwen
where fariqi> dateadd(day,-90,getdate())
用時:2423毫秒(2秒)
雖然每條語句提取出來的都是25萬條數據,各種情況的差異卻是巨大的,特別是將聚集索引建立在日期列時的差異。事實上,如果您的數據庫真的有1000萬容量的話,把主鍵建立在ID列上,就像以上的第1、2種情況,在網頁上的表現就是超時,根本就無法顯示。這也是我摒棄ID列作為聚集索引的一個最重要的因素。
得出以上速度的方法是:在各個select語句前加:declare @d datetime
set @d=getdate()
并在select語句后加:
select [語句執行花費時間(毫秒)]=datediff(ms,@d,getdate())
2、只要建立索引就能顯著提高查詢速度
事實上,我們可以發現上面的例子中,第2、3條語句完全相同,且建立索引的字段也相同;不同的僅是前者在fariqi字段上建立的是非聚合索引,后者在此字段上建立的是聚合索引,但查詢速度卻有著天壤之別。所以,并非是在任何字段上簡單地建立索引就能提高查詢速度。
從建表的語句中,我們可以看到這個有著1000萬數據的表中fariqi字段有5003個不同記錄。在此字段上建立聚合索引是再合適不過了。在現實中,我們每天都會發幾個文件,這幾個文件的發文日期就相同,這完全符合建立聚集索引要求的:“既不能絕大多數都相同,又不能只有極少數相同”的規則。由此看來,我們建立“適當”的聚合索引對于我們提高查詢速度是非常重要的。
3、把所有需要提高查詢速度的字段都加進聚集索引,以提高查詢速度
上面已經談到:在進行數據查詢時都離不開字段的是“日期”還有用戶本身的“用戶名”。既然這兩個字段都是如此的重要,我們可以把他們合并起來,建立一個復合索引(compound index)。
很多人認為只要把任何字段加進聚集索引,就能提高查詢速度,也有人感到迷惑:如果把復合的聚集索引字段分開查詢,那么查詢速度會減慢嗎?帶著這個問題,我們來看一下以下的查詢速度(結果集都是25萬條數據):(日期列fariqi首先排在復合聚集索引的起始列,用戶名neibuyonghu排在后列)
(1)select gid,fariqi,neibuyonghu,title from Tgongwen where fariqi>'2004-5-5'
查詢速度:2513毫秒
(2)select gid,fariqi,neibuyonghu,title from Tgongwen where fariqi>'2004-5-5' and neibuyonghu='辦公室'
查詢速度:2516毫秒
(3)select gid,fariqi,neibuyonghu,title from Tgongwen where neibuyonghu='辦公室'
查詢速度:60280毫秒
從以上試驗中,我們可以看到如果僅用聚集索引的起始列作為查詢條件和同時用到復合聚集索引的全部列的查詢速度是幾乎一樣的,甚至比用上全部的復合索引列還要略快(在查詢結果集數目一樣的情況下);而如果僅用復合聚集索引的非起始列作為查詢條件的話,這個索引是不起任何作用的。當然,語句1、2的查詢速度一樣是因為查詢的條目數一樣,如果復合索引的所有列都用上,而且查詢結果少的話,這樣就會形成“索引覆蓋”,因而性能可以達到最優。同時,請記住:無論您是否經常使用聚合索引的其他列,但其前導列一定要是使用最頻繁的列。
(四)其他書上沒有的索引使用經驗總結
1、用聚合索引比用不是聚合索引的主鍵速度快
下面是實例語句:(都是提取25萬條數據)
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16'
使用時間:3326毫秒
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where gid<=250000
使用時間:4470毫秒
這里,用聚合索引比用不是聚合索引的主鍵速度快了近1/4。
2、用聚合索引比用一般的主鍵作order by時速度快,特別是在小數據量情況下
select gid,fariqi,neibuyonghu,reader,title from Tgongwen order by fariqi
用時:12936
select gid,fariqi,neibuyonghu,reader,title from Tgongwen order by gid
用時:18843
這里,用聚合索引比用一般的主鍵作order by時,速度快了3/10。事實上,如果數據量很小的話,用聚集索引作為排序列要比使用非聚集索引速度快得明顯的多;而數據量如果很大的話,如10萬以上,則二者的速度差別不明顯。
3、使用聚合索引內的時間段,搜索時間會按數據占整個數據表的百分比成比例減少,而無論聚合索引使用了多少個
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>'2004-1-1'
用時:6343毫秒(提取100萬條)
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>'2004-6-6'
用時:3170毫秒(提取50萬條)
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16'
用時:3326毫秒(和上句的結果一模一樣。如果采集的數量一樣,那么用大于號和等于號是一樣的)
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>'2004-1-1' and fariqi<'2004-6-6'
用時:3280毫秒
4 、日期列不會因為有分秒的輸入而減慢查詢速度
下面的例子中,共有100萬條數據,2004年1月1日以后的數據有50萬條,但只有兩個不同的日期,日期精確到日;之前有數據50萬條,有5000個不同的日期,日期精確到秒。
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>'2004-1-1' order by fariqi
用時:6390毫秒
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi<'2004-1-1' order by fariqi
用時:6453毫秒
(五)其他注意事項
“水可載舟,亦可覆舟”,索引也一樣。索引有助于提高檢索性能,但過多或不當的索引也會導致系統低效。因為用戶在表中每加進一個索引,數據庫就要做更多的工作。過多的索引甚至會導致索引碎片。
所以說,我們要建立一個“適當”的索引體系,特別是對聚合索引的創建,更應精益求精,以使您的數據庫能得到高性能的發揮。
當然,在實踐中,作為一個盡職的數據庫管理員,您還要多測試一些方案,找出哪種方案效率最高、最為有效。
二、改善SQL語句
很多人不知道SQL語句在SQL SERVER中是如何執行的,他們擔心自己所寫的SQL語句會被SQL SERVER誤解。比如:
select * from table1 where name='zhangsan' and tID > 10000
和執行:
select * from table1 where tID > 10000 and name='zhangsan'
一些人不知道以上兩條語句的執行效率是否一樣,因為如果簡單的從語句先后上看,這兩個語句的確是不一樣,如果tID是一個聚合索引,那么后一句僅僅從表的10000條以后的記錄中查找就行了;而前一句則要先從全表中查找看有幾個name='zhangsan'的,而后再根據限制條件條件tID>10000來提出查詢結果。
事實上,這樣的擔心是不必要的。SQL SERVER中有一個“查詢分析優化器”,它可以計算出where子句中的搜索條件并確定哪個索引能縮小表掃描的搜索空間,也就是說,它能實現自動優化。
雖然查詢優化器可以根據where子句自動的進行查詢優化,但大家仍然有必要了解一下“查詢優化器”的工作原理,如非這樣,有時查詢優化器就會不按照您的本意進行快速查詢。
在查詢分析階段,查詢優化器查看查詢的每個階段并決定限制需要掃描的數據量是否有用。如果一個階段可以被用作一個掃描參數(SARG),那么就稱之為可優化的,并且可以利用索引快速獲得所需數據。
SARG的定義:用于限制搜索的一個操作,因為它通常是指一個特定的匹配,一個值得范圍內的匹配或者兩個以上條件的AND連接。形式如下:
列名 操作符 <常數 或 變量>
或
<常數 或 變量> 操作符列名
列名可以出現在操作符的一邊,而常數或變量出現在操作符的另一邊。如:
Name=’張三’
價格>5000
5000<價格
Name=’張三’ and 價格>5000
如果一個表達式不能滿足SARG的形式,那它就無法限制搜索的范圍了,也就是SQL SERVER必須對每一行都判斷它是否滿足WHERE子句中的所有條件。所以一個索引對于不滿足SARG形式的表達式來說是無用的。
介紹完SARG后,我們來總結一下使用SARG以及在實踐中遇到的和某些資料上結論不同的經驗:
1、Like語句是否屬于SARG取決于所使用的通配符的類型
如:name like ‘張%’ ,這就屬于SARG
而:name like ‘%張’ ,就不屬于SARG。
原因是通配符%在字符串的開通使得索引無法使用。
2、or 會引起全表掃描
Name=’張三’ and 價格>5000 符號SARG,而:Name=’張三’ or 價格>5000 則不符合SARG。使用or會引起全表掃描。
3、非操作符、函數引起的不滿足SARG形式的語句
不滿足SARG形式的語句最典型的情況就是包括非操作符的語句,如:NOT、!=、<>、!<、!>、NOT EXISTS、NOT IN、NOT LIKE等,另外還有函數。下面就是幾個不滿足SARG形式的例子:
ABS(價格)<5000
Name like ‘%三’
有些表達式,如:
WHERE 價格*2>5000
SQL SERVER也會認為是SARG,SQL SERVER會將此式轉化為:
WHERE 價格>2500/2
但我們不推薦這樣使用,因為有時SQL SERVER不能保證這種轉化與原始表達式是完全等價的。
4、IN 的作用相當與OR
語句:
Select * from table1 where tid in (2,3)
和
Select * from table1 where tid=2 or tid=3
是一樣的,都會引起全表掃描,如果tid上有索引,其索引也會失效。
5、盡量少用NOT
6、exists 和 in 的執行效率是一樣的
很多資料上都顯示說,exists要比in的執行效率要高,同時應盡可能的用not exists來代替not in。但事實上,我試驗了一下,發現二者無論是前面帶不帶not,二者之間的執行效率都是一樣的。因為涉及子查詢,我們試驗這次用SQL SERVER自帶的pubs數據庫。運行前我們可以把SQL SERVER的statistics I/O狀態打開。
(1)select title,price from titles where title_id in (select title_id from sales where qty>30)
該句的執行結果為:
表 'sales'。掃描計數 18,邏輯讀 56 次,物理讀 0 次,預讀 0 次。
表 'titles'。掃描計數 1,邏輯讀 2 次,物理讀 0 次,預讀 0 次。
(2)select title,price from titles where exists (select * from sales where sales.title_id=titles.title_id and qty>30)
第二句的執行結果為:
表 'sales'。掃描計數 18,邏輯讀 56 次,物理讀 0 次,預讀 0 次。
表 'titles'。掃描計數 1,邏輯讀 2 次,物理讀 0 次,預讀 0 次。
我們從此可以看到用exists和用in的執行效率是一樣的。
7、用函數charindex()和前面加通配符%的LIKE執行效率一樣
前面,我們談到,如果在LIKE前面加上通配符%,那么將會引起全表掃描,所以其執行效率是低下的。但有的資料介紹說,用函數charindex()來代替LIKE速度會有大的提升,經我試驗,發現這種說明也是錯誤的:
select gid,title,fariqi,reader from tgongwen where charindex('刑偵支隊',reader)>0 and fariqi>'2004-5-5'
用時:7秒,另外:掃描計數 4,邏輯讀 7155 次,物理讀 0 次,預讀 0 次。
select gid,title,fariqi,reader from tgongwen where reader like '%' + '刑偵支隊' + '%' and fariqi>'2004-5-5'
用時:7秒,另外:掃描計數 4,邏輯讀 7155 次,物理讀 0 次,預讀 0 次。
8、union并不絕對比or的執行效率高
我們前面已經談到了在where子句中使用or會引起全表掃描,一般的,我所見過的資料都是推薦這里用union來代替or。事實證明,這種說法對于大部分都是適用的。
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16' or gid>9990000
用時:68秒。掃描計數 1,邏輯讀 404008 次,物理讀 283 次,預讀 392163 次。
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16'
union
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where gid>9990000
用時:9秒。掃描計數 8,邏輯讀 67489 次,物理讀 216 次,預讀 7499 次。
看來,用union在通常情況下比用or的效率要高的多。
但經過試驗,筆者發現如果or兩邊的查詢列是一樣的話,那么用union則反倒和用or的執行速度差很多,雖然這里union掃描的是索引,而or掃描的是全表。
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16' or fariqi='2004-2-5'
用時:6423毫秒。掃描計數 2,邏輯讀 14726 次,物理讀 1 次,預讀 7176 次。
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16'
union
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-2-5'
用時:11640毫秒。掃描計數 8,邏輯讀 14806 次,物理讀 108 次,預讀 1144 次。
9、字段提取要按照“需多少、提多少”的原則,避免“select *”
我們來做一個試驗:
select top 10000 gid,fariqi,reader,title from tgongwen order by gid desc
用時:4673毫秒
select top 10000 gid,fariqi,title from tgongwen order by gid desc
用時:1376毫秒
select top 10000 gid,fariqi from tgongwen order by gid desc
用時:80毫秒
由此看來,我們每少提取一個字段,數據的提取速度就會有相應的提升。提升的速度還要看您舍棄的字段的大小來判斷。
10、count(*)不比count(字段)慢
某些資料上說:用*會統計所有列,顯然要比一個世界的列名效率低。這種說法其實是沒有根據的。我們來看:
select count(*) from Tgongwen
用時:1500毫秒
select count(gid) from Tgongwen
用時:1483毫秒
select count(fariqi) from Tgongwen
用時:3140毫秒
select count(title) from Tgongwen
用時:52050毫秒
從以上可以看出,如果用count(*)和用count(主鍵)的速度是相當的,而count(*)卻比其他任何除主鍵以外的字段匯總速度要快,而且字段越長,匯總的速度就越慢。我想,如果用count(*), SQL SERVER可能會自動查找最小字段來匯總的。當然,如果您直接寫count(主鍵)將會來的更直接些。
11、order by按聚集索引列排序效率最高
我們來看:(gid是主鍵,fariqi是聚合索引列)
select top 10000 gid,fariqi,reader,title from tgongwen
用時:196 毫秒。 掃描計數 1,邏輯讀 289 次,物理讀 1 次,預讀 1527 次。
select top 10000 gid,fariqi,reader,title from tgongwen order by gid asc
用時:4720毫秒。 掃描計數 1,邏輯讀 41956 次,物理讀 0 次,預讀 1287 次。
select top 10000 gid,fariqi,reader,title from tgongwen order by gid desc
用時:4736毫秒。 掃描計數 1,邏輯讀 55350 次,物理讀 10 次,預讀 775 次。
select top 10000 gid,fariqi,reader,title from tgongwen order by fariqi asc
用時:173毫秒。 掃描計數 1,邏輯讀 290 次,物理讀 0 次,預讀 0 次。
select top 10000 gid,fariqi,reader,title from tgongwen order by fariqi desc
用時:156毫秒。 掃描計數 1,邏輯讀 289 次,物理讀 0 次,預讀 0 次。
從以上我們可以看出,不排序的速度以及邏輯讀次數都是和“order by 聚集索引列” 的速度是相當的,但這些都比“order by 非聚集索引列”的查詢速度是快得多的。
同時,按照某個字段進行排序的時候,無論是正序還是倒序,速度是基本相當的。
12、高效的TOP
事實上,在查詢和提取超大容量的數據集時,影響數據庫響應時間的最大因素不是數據查找,而是物理的I/0操作。如:
select top 10 * from (
select top 10000 gid,fariqi,title from tgongwen
where neibuyonghu='辦公室'
order by gid desc) as a
order by gid asc
這條語句,從理論上講,整條語句的執行時間應該比子句的執行時間長,但事實相反。因為,子句執行后返回的是10000條記錄,而整條語句僅返回10條語句,所以影響數據庫響應時間最大的因素是物理I/O操作。而限制物理I/O操作此處的最有效方法之一就是使用TOP關鍵詞了。TOP關鍵詞是SQL SERVER中經過系統優化過的一個用來提取前幾條或前幾個百分比數據的詞。經筆者在實踐中的應用,發現TOP確實很好用,效率也很高。但這個詞在另外一個大型數據庫ORACLE中卻沒有,這不能說不是一個遺憾,雖然在ORACLE中可以用其他方法(如:rownumber)來解決。在以后的關于“實現千萬級數據的分頁顯示存儲過程”的討論中,我們就將用到TOP這個關鍵詞。
到此為止,我們上面討論了如何實現從大容量的數據庫中快速地查詢出您所需要的數據方法。當然,我們介紹的這些方法都是“軟”方法,在實踐中,我們還要考慮各種“硬”因素,如:網絡性能、服務器的性能、操作系統的性能,甚至網卡、交換機等。
三、實現小數據量和海量數據的通用分頁顯示存儲過程
建立一個web 應用,分頁瀏覽功能必不可少。這個問題是數據庫處理中十分常見的問題。經典的數據分頁方法是:ADO 紀錄集分頁法,也就是利用ADO自帶的分頁功能(利用游標)來實現分頁。但這種分頁方法僅適用于較小數據量的情形,因為游標本身有缺點:游標是存放在內存中,很費內存。游標一建立,就將相關的記錄鎖住,直到取消游標。游標提供了對特定集合中逐行掃描的手段,一般使用游標來逐行遍歷數據,根據取出數據條件的不同進行不同的操作。而對于多表和大表中定義的游標(大的數據集合)循環很容易使程序進入一個漫長的等待甚至死機。
更重要的是,對于非常大的數據模型而言,分頁檢索時,如果按照傳統的每次都加載整個數據源的方法是非常浪費資源的。現在流行的分頁方法一般是檢索頁面大小的塊區的數據,而非檢索所有的數據,然后單步執行當前行。
最早較好地實現這種根據頁面大小和頁碼來提取數據的方法大概就是“俄羅斯存儲過程”。這個存儲過程用了游標,由于游標的局限性,所以這個方法并沒有得到大家的普遍認可。
后來,網上有人改造了此存儲過程,下面的存儲過程就是結合我們的辦公自動化實例寫的分頁存儲過程:
CREATE procedure pagination1
(@pagesize int, --頁面大小,如每頁存儲20條記錄
@pageindex int --當前頁碼
)
as
set nocount on
begin
declare @indextable table(id int identity(1,1),nid int) --定義表變量
declare @PageLowerBound int --定義此頁的底碼
declare @PageUpperBound int --定義此頁的頂碼
set @PageLowerBound=(@pageindex-1)*@pagesize
set @PageUpperBound=@PageLowerBound+@pagesize
set rowcount @PageUpperBound
insert into @indextable(nid) select gid from TGongwen where fariqi >dateadd(day,-365,getdate()) order by fariqi desc
select O.gid,O.mid,O.title,O.fadanwei,O.fariqi from TGongwen O,@indextable t where O.gid=t.nid
and t.id>@PageLowerBound and t.id<=@PageUpperBound order by t.id
end
set nocount off
以上存儲過程運用了SQL SERVER的最新技術――表變量。應該說這個存儲過程也是一個非常優秀的分頁存儲過程。當然,在這個過程中,您也可以把其中的表變量寫成臨時表:CREATE TABLE #Temp。但很明顯,在SQL SERVER中,用臨時表是沒有用表變量快的。所以筆者剛開始使用這個存儲過程時,感覺非常的不錯,速度也比原來的ADO的好。但后來,我又發現了比此方法更好的方法。
筆者曾在網上看到了一篇小短文《從數據表中取出第n條到第m條的記錄的方法》,全文如下:
從publish 表中取出第 n 條到第 m 條的記錄:
SELECT TOP m-n+1 *
FROM publish
WHERE (id NOT IN
(SELECT TOP n-1 id
FROM publish))
id 為publish 表的關鍵字
我當時看到這篇文章的時候,真的是精神為之一振,覺得思路非常得好。等到后來,我在作辦公自動化系統(ASP.NET+ C#+SQL SERVER)的時候,忽然想起了這篇文章,我想如果把這個語句改造一下,這就可能是一個非常好的分頁存儲過程。于是我就滿網上找這篇文章,沒想到,文章還沒找到,卻找到了一篇根據此語句寫的一個分頁存儲過程,這個存儲過程也是目前較為流行的一種分頁存儲過程,我很后悔沒有爭先把這段文字改造成存儲過程:
CREATE PROCEDURE pagination2
(
@SQL nVARCHAR(4000), --不帶排序語句的SQL語句
@Page int, --頁碼
@RecsPerPage int, --每頁容納的記錄數
@ID VARCHAR(255), --需要排序的不重復的ID號
@Sort VARCHAR(255) --排序字段及規則
)
AS
DECLARE @Str nVARCHAR(4000)
SET @Str='SELECT TOP '+CAST(@RecsPerPage AS VARCHAR(20))+' * FROM ('+@SQL+') T WHERE T.'+@ID+'NOT IN
(SELECT TOP '+CAST((@RecsPerPage*(@Page-1)) AS VARCHAR(20))+' '+@ID+' FROM ('+@SQL+') T9 ORDER BY '+@Sort+') ORDER BY '+@Sort
PRINT @Str
EXEC sp_ExecuteSql @Str
GO
其實,以上語句可以簡化為:
SELECT TOP 頁大小 *
FROM Table1
WHERE (ID NOT IN
(SELECT TOP 頁大小*頁數 id
FROM 表
ORDER BY id))
ORDER BY ID
但這個存儲過程有一個致命的缺點,就是它含有NOT IN字樣。雖然我可以把它改造為:
SELECT TOP 頁大小 *
FROM Table1
WHERE not exists
(select * from (select top (頁大小*頁數) * from table1 order by id) b where b.id=a.id )
order by id
即,用not exists來代替not in,但我們前面已經談過了,二者的執行效率實際上是沒有區別的。
既便如此,用TOP 結合NOT IN的這個方法還是比用游標要來得快一些。
雖然用not exists并不能挽救上個存儲過程的效率,但使用SQL SERVER中的TOP關鍵字卻是一個非常明智的選擇。因為分頁優化的最終目的就是避免產生過大的記錄集,而我們在前面也已經提到了TOP的優勢,通過TOP 即可實現對數據量的控制。
在分頁算法中,影響我們查詢速度的關鍵因素有兩點:TOP和NOT IN。TOP可以提高我們的查詢速度,而NOT IN會減慢我們的查詢速度,所以要提高我們整個分頁算法的速度,就要徹底改造NOT IN,同其他方法來替代它。
我們知道,幾乎任何字段,我們都可以通過max(字段)或min(字段)來提取某個字段中的最大或最小值,所以如果這個字段不重復,那么就可以利用這些不重復的字段的max或min作為分水嶺,使其成為分頁算法中分開每頁的參照物。在這里,我們可以用操作符“>”或“<”號來完成這個使命,使查詢語句符合SARG形式。如:
Select top 10 * from table1 where id>200
于是就有了如下分頁方案:
select top 頁大小 *
from table1
where id>
(select max (id) from
(select top ((頁碼-1)*頁大小) id from table1 order by id) as T
)
order by id
在選擇即不重復值,又容易分辨大小的列時,我們通常會選擇主鍵。下表列出了筆者用有著1000萬數據的辦公自動化系統中的表,在以GID(GID是主鍵,但并不是聚集索引。)為排序列、提取gid,fariqi,title字段,分別以第1、10、100、500、1000、1萬、10萬、25萬、50萬頁為例,測試以上三種分頁方案的執行速度:(單位:毫秒)
頁 碼
方案1
方案2
方案3
1
60
30
76
10
46
16
63
100
1076
720
130
500
540
12943
83
1000
17110
470
250
1萬
24796
4500
140
10萬
38326
42283
1553
25萬
28140
128720
2330
50萬
121686
127846
7168
從上表中,我們可以看出,三種存儲過程在執行100頁以下的分頁命令時,都是可以信任的,速度都很好。但第一種方案在執行分頁1000頁以上后,速度就降了下來。第二種方案大約是在執行分頁1萬頁以上后速度開始降了下來。而第三種方案卻始終沒有大的降勢,后勁仍然很足。
在確定了第三種分頁方案后,我們可以據此寫一個存儲過程。大家知道SQL SERVER的存儲過程是事先編譯好的SQL語句,它的執行效率要比通過WEB頁面傳來的SQL語句的執行效率要高。下面的存儲過程不僅含有分頁方案,還會根據頁面傳來的參數來確定是否進行數據總數統計。
-- 獲取指定頁的數據
CREATE PROCEDURE pagination3
@tblName varchar(255), -- 表名
@strGetFields varchar(1000) = '*', -- 需要返回的列
@fldName varchar(255)='', -- 排序的字段名
@PageSize int = 10, -- 頁尺寸
@PageIndex int = 1, -- 頁碼
@doCount bit = 0, -- 返回記錄總數, 非 0 值則返回
@OrderType bit = 0, -- 設置排序類型, 非 0 值則降序
@strWhere varchar(1500) = '' -- 查詢條件 (注意: 不要加 where)
AS
declare @strSQL varchar(5000) -- 主語句
declare @strTmp varchar(110) -- 臨時變量
declare @strOrder varchar(400) -- 排序類型
if @doCount != 0
begin
if @strWhere !=''
set @strSQL = "select count(*) as Total from [" + @tblName + "] where "+@strWhere
else
set @strSQL = "select count(*) as Total from [" + @tblName + "]"
end
--以上代碼的意思是如果@doCount傳遞過來的不是0,就執行總數統計。以下的所有代碼都是@doCount為0的情況
else
begin
if @OrderType != 0
begin
set @strTmp = "<(select min"
set @strOrder = " order by [" + @fldName +"] desc"
--如果@OrderType不是0,就執行降序,這句很重要!
end
else
begin
set @strTmp = ">(select max"
set @strOrder = " order by [" + @fldName +"] asc"
end
if @PageIndex = 1
begin
if @strWhere != ''
set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ " from [" + @tblName + "] where " + @strWhere + " " + @strOrder
else
set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ " from ["+ @tblName + "] "+ @strOrder
--如果是第一頁就執行以上代碼,這樣會加快執行速度
end
else
begin
--以下代碼賦予了@strSQL以真正執行的SQL代碼
set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ " from ["
+ @tblName + "] where [" + @fldName + "]" + @strTmp + "(["+ @fldName + "]) from (select top " + str((@PageIndex-1)*@PageSize) + " ["+ @fldName + "] from [" + @tblName + "]" + @strOrder + ") as tblTmp)"+ @strOrder
if @strWhere != ''
set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ " from ["
+ @tblName + "] where [" + @fldName + "]" + @strTmp + "(["
+ @fldName + "]) from (select top " + str((@PageIndex-1)*@PageSize) + " ["
+ @fldName + "] from [" + @tblName + "] where " + @strWhere + " "
+ @strOrder + ") as tblTmp) and " + @strWhere + " " + @strOrder
end
end
exec (@strSQL)
GO
上面的這個存儲過程是一個通用的存儲過程,其注釋已寫在其中了。
在大數據量的情況下,特別是在查詢最后幾頁的時候,查詢時間一般不會超過9秒;而用其他存儲過程,在實踐中就會導致超時,所以這個存儲過程非常適用于大容量數據庫的查詢。
筆者希望能夠通過對以上存儲過程的解析,能給大家帶來一定的啟示,并給工作帶來一定的效率提升,同時希望同行提出更優秀的實時數據分頁算法。
四、聚集索引的重要性和如何選擇聚集索引
在上一節的標題中,筆者寫的是:實現小數據量和海量數據的通用分頁顯示存儲過程。這是因為在將本存儲過程應用于“辦公自動化”系統的實踐中時,筆者發現這第三種存儲過程在小數據量的情況下,有如下現象:
1、分頁速度一般維持在1秒和3秒之間。
2、在查詢最后一頁時,速度一般為5秒至8秒,哪怕分頁總數只有3頁或30萬頁。
雖然在超大容量情況下,這個分頁的實現過程是很快的,但在分前幾頁時,這個1-3秒的速度比起第一種甚至沒有經過優化的分頁方法速度還要慢,借用戶的話說就是“還沒有ACCESS數據庫速度快”,這個認識足以導致用戶放棄使用您開發的系統。
筆者就此分析了一下,原來產生這種現象的癥結是如此的簡單,但又如此的重要:排序的字段不是聚集索引!
本篇文章的題目是:“查詢優化及分頁算法方案”。筆者只所以把“查詢優化”和“分頁算法”這兩個聯系不是很大的論題放在一起,就是因為二者都需要一個非常重要的東西――聚集索引。
在前面的討論中我們已經提到了,聚集索引有兩個最大的優勢:
1、以最快的速度縮小查詢范圍。
2、以最快的速度進行字段排序。
第1條多用在查詢優化時,而第2條多用在進行分頁時的數據排序。
而聚集索引在每個表內又只能建立一個,這使得聚集索引顯得更加的重要。聚集索引的挑選可以說是實現“查詢優化”和“高效分頁”的最關鍵因素。
但要既使聚集索引列既符合查詢列的需要,又符合排序列的需要,這通常是一個矛盾。
筆者前面“索引”的討論中,將fariqi,即用戶發文日期作為了聚集索引的起始列,日期的精確度為“日”。這種作法的優點,前面已經提到了,在進行劃時間段的快速查詢中,比用ID主鍵列有很大的優勢。
但在分頁時,由于這個聚集索引列存在著重復記錄,所以無法使用max或min來最為分頁的參照物,進而無法實現更為高效的排序。而如果將ID主鍵列作為聚集索引,那么聚集索引除了用以排序之外,沒有任何用處,實際上是浪費了聚集索引這個寶貴的資源。
為解決這個矛盾,筆者后來又添加了一個日期列,其默認值為getdate()。用戶在寫入記錄時,這個列自動寫入當時的時間,時間精確到毫秒。即使這樣,為了避免可能性很小的重合,還要在此列上創建UNIQUE約束。將此日期列作為聚集索引列。
有了這個時間型聚集索引列之后,用戶就既可以用這個列查找用戶在插入數據時的某個時間段的查詢,又可以作為唯一列來實現max或min,成為分頁算法的參照物。
經過這樣的優化,筆者發現,無論是大數據量的情況下還是小數據量的情況下,分頁速度一般都是幾十毫秒,甚至0毫秒。而用日期段縮小范圍的查詢速度比原來也沒有任何遲鈍。
聚集索引是如此的重要和珍貴,所以筆者總結了一下,一定要將聚集索引建立在:
1、您最頻繁使用的、用以縮小查詢范圍的字段上;
2、您最頻繁使用的、需要排序的字段上。
結束語:
本篇文章匯集了筆者近段在使用數據庫方面的心得,是在做“辦公自動化”系統時實踐經驗的積累。希望這篇文章不僅能夠給大家的工作帶來一定的幫助,也希望能讓大家能夠體會到分析問題的方法;最重要的是,希望這篇文章能夠拋磚引玉,掀起大家的學習和討論的興趣,以共同促進,共同為公安科技強警事業和金盾工程做出自己最大的努力。
最后需要說明的是,在試驗中,我發現用戶在進行大數據量查詢的時候,對數據庫速度影響最大的不是內存大小,而是CPU。在我的P4 2.4機器上試驗的時候,查看“資源管理器”,CPU經常出現持續到100%的現象,而內存用量卻并沒有改變或者說沒有大的改變。即使在我們的HP ML 350 G3服務器上試驗時,CPU峰值也能達到90%,一般持續在70%左右。
導讀
本文是我對學習jwsdp-1.2時所做筆記的整理,其中主要是一些指導性的內容,并沒有多少概念以及原理的介紹,讀者可能覺得略顯簡單,如果想要學習基本概念可以參考網上有關Web Service的資料。本文例子所使用的開發環境是WindowXP+JWSDP-1.2。
一.Web Service簡介
1.定義
由兩部分組成
·SOAP--Web Service之間的基本通信協議。
·WSDL--Web Service描述語言,它定義了Web Service做什么,怎么做和查詢的信息。
2.簡單的Web Service實現
包含四個基本步驟
·創建Web Service的商業邏輯(通常是一些Java類)
·將這些Java類部署到一個SOAP服務器上
·生成客戶訪問代碼
·部署客戶應用
注意:WSDL等文件的生成通常是利用廠商提供的工具來完成
3.WSDL解析
WSDL描述語言一般包含三部分
·What部分--包括了type、message和portType元素
Type:定義了Web Service使用的數據結構(使用XML Schema定義)
Message:一個Message是SOAP的基本通信元素。每個Message可以有一個或多個Part,每個Part代表一個參數。
PortType:消息匯總為不同的操作并歸入到一個被稱為portType的實體中。一個portType代表一個接口(Web Service支 持的操作集合),每個Web Service可以有多個接口,它們都使用portType表示。每個操作又包含了input和 output部分。
·How部分--包含binding元素
binding元素將portType綁定到特定的通信協議上(如HTTP上的SOAP協議)
·Where部分--由service元素組成
它將portType,binding以及Web Service實際的位置(URI)放在一起描述
4.客戶端
通常Web Service可以有三種類型的客戶
·商業伙伴(Business Partner)--包括分發商,零售商以及大型消費者)
此類客戶通過SOAP、WSDL、ebXML、UDDI等XML技術與Web Service連接
·瘦客戶--包括Web瀏覽器、PDA以及無線設備
該類客戶通常經由輕量協議(如HTTP)與Web Service連接
·肥客戶--包括Applet、各類應用以及現存系統
通常使用重量級協議(如IIOP)連接Web Service
二.使用JAX-RPC開發Web Service
1.JAX-RPC支持的數據類型
JAX-RPC除了支持Java的基本數據類型外還支持一些自定義對象,但這些對象有一些條件限制
·有缺省構造函數的對象
·沒有實現java.rmi.Remote接口
·字段必須是JAX-RPC支持的類型
·公有字段不能聲明為final或transient
·非公有字段必須有對應的setter和getter方法
2.使用JAX-RPC創建Web Service
·基本步驟
A. 編寫服務端接口并實現
一個服務的end-point有一些規定:必須實現java.rmi.Remot接口而且每個方法需要拋出RemoteException異常。
B. 編譯、生成并且將所有服務需要的類和文件打包成WAR文件
C. 部署包含服務的WAR文件
·如何創建服務
A. 編譯服務所需的類文件
B. 生成服務所需文件
可以使用wscompile工具生成model.gz文件,它包含了描述服務的內部數據結構命令如下
wscompile -define -d build -nd build -classpath build config.xml
-model build/model.gz
define標志告訴工具讀取服務的 endpoint接口并且創建WSDL文件。-d和-nd標志告訴工具將輸出文件寫入指定的目錄build。工具需要讀以下的config.xml文件
<?xml version=”1.0” encoding=”UTF-8”?>
<configuration xmlns=”http://java.sun.com/xml/ns/jax-rpc/ri/config”>
<service
name=”HelloService”
targetNamespace=”urn:Star”
typeNamespace=”urn:Star”
packageName=”helloservice”>
<interface name=”helloservice.HelloIF”/>
</service>
</configuration>
該文件告訴wscompile創建model文件所需的信息
·服務名稱:MyHelloService
·WSDL名字空間:urn:Star
·HelloService的所有類在包helloservice中
·服務的端點(endpoint)接口:helloservice.HelloIF
C. 將服務打包為WAR文件
WEB-INF/classes/hello/HelloIF.class
WEB-INF/classes/hello/HelloImpl.class
WEB-INF/jaxrpc-ri.xml
WEB-INF/model.gz
WEB-INF/web.xml
jaxrpc-ri.xml文件如下所述
<?xml version=”1.0” encoding=”UTF-8”?>
<webServices xmlns=”http://java.sun.com/xml/ns/jax-rpc/ri/dd”
version=”1.0”
targetNamespaceBase=”urn:Star”
typeNamespaceBase=”urn:Star”
urlPatternBase=”webservice”>
<endpoint name=”Hello”
displayName=”HelloWorld Service”
description=”A simple web service”
interface=”helloservice.HelloIF”
model=”/WEB-INF/model.gz”
implementation=”helloservice.HelloImpl”/>
<endpointMapping endpointName=”Hello” urlPattern=”/hello”/>
</webServices>
D. 處理WAR文件
使用命令行
wsdeploy -o hello-jaxrpc.war hello-jaxrpc-original.war
wsdeploy工具完成以下幾個任務
·讀 hello-jaxrpc-original.war作為輸入
·從jaxrpc-ri.xml文件中獲得信息
·為服務生成tie classes
·生成名為HelloService.wsdl的WSDL文件
·將tie classes和HelloService.wsdl文件打包到新的war文件中
E. 在服務器上部署服務
如果你使用的是TOMCAT,你可以將WAR文件拷貝到webapps目錄下,然后可以在
<http://localhost:8080/[context]/[servicename>上看是否配置成功
·如何使用JAX-RPC創建Web Service客戶端
通常有三種類型的客戶:Static Stub、Dynamic Proxy和Dynamic Invocation Interface(DII)
Static Stub客戶
·生成Stub
通過使用config-wsdl.xml和wscompile工具,可以生成stub
wscompile -gen:client -d build -classpath build config-wsdl.xml
config-wsdl.xml文件如下
<?xml version="1.0" encoding="UTF-8"?>
<configuration xmlns="http://java.sun.com/xml/ns/jax-rpc/ri/config">
<wsdl location="http://localhost:8080/helloWS/hello?WSDL"
packageName="staticstub"/>
</configuration>
wscompile工具讀取服務器上的WSDL文件并生成stub
·編寫靜態客戶代碼
Stub stub=(Stub)(new HelloService_Impl().getHelloIFPort());
HelloIF hello=(HelloIF)stub;
Hello.sayHello(“starchu”);
注意:HelloService_Impl類由wscompile生成
·編譯代碼
·運行客戶端(需要包含saaj API和JAX-RPC API運行)
Dynamic Proxy客戶
·生成接口
通過使用config-wsdl.xml文件和wscompile工具,可以生成客戶所需的接口
wscompile -import -d build -classpath build config-wsdl.xml
config-wsdl.xml和前面列出的文件內容相同。
·編寫動態客戶代碼
ServiceFactory factory=ServiceFactory.newInstance();
URL wsdlUrl=new URL(“<your web service wsdl url>”);
Service service=factory.createService(wsdlUrl,
new QName(“urn:Star”,”HelloService”));
HelloIF hello=(HelloIF)service.getPort(
new QName(“urn:Star”,”HelloIFPort”),HelloIF.class);
Hello.sayHello(“starchu”);
注意:這里不再需要靜態客戶代碼的HelloService_Impl類
·編譯代碼
·運行客戶端(需要包含saaj API和JAX-RPC API運行)
Dynamic Invocation Interface客戶
這個方法為我們提供了更有彈性的客戶調用方式,客戶代碼不在需要由wscompile工具生成的運行時類,當然這種代碼更加復雜。具體步驟如下:
·創建ServiceFactory實例
ServiceFactory factory=ServiceFactory.newInstance();
·創建Service(利用服務名的Qname)
Service service=factory.createService(new QName(“HelloService”));
·創建Call對象(使用端點接口的Qname)
Call call=service.createCall(new QName(“HelloIF”));
·設置端點的地址和一些Call對象屬性
call.setTargetEndpointAddress(args[0]);
call.setProperty(Call.SOAPACTION_USE_PROPERTY,new Boolean(true));
call.setProperty(Call.SOAPACTION_URI_PROPERTY,””);
call.setProperty(“javax.xml.rpc.encodingstyle.namespace.uri”,
“http://schemas.xmlsoap.org/soap/encoding/”);
·設置遠程調用的返回類型、操作名和參數
QName stringType=new Qname(“http://www.w3.org/2001/XMLSchema”,”string”)
call.setReturnType(stringType);
call.setOperationName(new Qname(“urn:Star”,”sayHello”));
call.addParameter(“String_1”,stringType,ParameterMode.IN);
·調用call的invoke方法
String [] param={ “ starchu “ };
String retValue=call.invoke(param);
·編譯代碼并對Main方法設置<http://localhost:8080/helloWS/hello參數(服務器需有效>)
3.SOAP Message Handler的例子
通常使用JAX-RPC建立的Web Service并不需要開發人員自己處理SOAP消息,但是JAX-RPC提供了一種機制可以使程序員獲得這種處理能力,這就是所謂的消息處理器。總的來說,像日志和加解密功能可以通過SOAP消息處理器實現,除此之外,你根本不需要處理SOAP消息。
·基本Handler處理過程
SOAP請求
·客戶端處理器在請求消息發送到服務器前被調用
·服務端處理器在請求消息分發到端點前被調用
SOAP應答
·服務端處理器在應答消息發送回客戶前被調用
·客戶端處理器在應答消息轉換成Java方法返回前被調用
SOAP錯誤
處理過程與SOAP應答的方式一樣
注意:處理器可以在任意端組成處理器鏈
A.Handler基本編程模型
服務端
·編寫服務端點接口代碼、實現服務并且實現服務端處理器類
·創建jaxrpc-ri.xml文件,以便wscompile使用,其中包含了Handler的信息
·創建web.xml文件
·編譯所有代碼
·將文件打包為WAR文件
·用wsdeploy工具將原始war文件替換為完整可部署的war文件
·在服務器上部署war文件
客戶端
·編寫客戶程序以及客戶端處理器代碼
·創建config.xml文件以便wscompile使用,它包含了客戶端處理器的信息
·編譯代碼
·運行wscompile生成服務端點接口和客戶類
·編譯所有代碼,并運行客戶應用
B.建立客戶端處理器
處理器必須擴展javax.xml.rpc.handler.GenericHandler類并且提供至少兩個方法的實現init和getHandlers。此外,你可以利用handleXXX方法處理請求、應答和錯誤SOAP消息。基本步驟如下
·編寫客戶端處理器代碼
Public class ClientHandler extends GenericHandler{
Public void init(HandlerInfo info){
This.info=info;
}
public QName[] getHeaders(){
return info.getHeaders();
}
public boolean handleRequest(MessageContext context){
SOAPMessageContext smc=(SOAPMessageContext)context;
SOAPMessage message=smc.getMessage();
file://You can use SOAP API to implement your own logic
file://such as logging and encrypt
……
file://Set a logger element in the SOAPHeader
SOAPHeaderElement loggerElement=
header.addHeaderElement(envelope.createName(“loginfo”,
“ns1”,”urn:Star:headprops”));
loggerElement.setMustUnderstand(true);
loggerElement.setValue(“10”);
file://Set a name element in the SOAP Header
SOAPHeaderElement nameElement=
Header.addHeaderElement(envelope.createName(“client”,
“ns1”,”urn:Star:headprops”));
nameElement.addTextNode(“Star chu”);
}
}
·編輯config.xml文件
<?xml version=”1.0” encoding=”UTF-8”?>
<configuration xmlns=”http://java.sun.com/xml/ns/jax-rpc/ri/config”?>
<wsdl location=”http://localhost:8080/handlerWS/handler?WSDL
packageName=”client”>
<handlerChains>
<chain runAt=”client”>
<handler className=”client.ClientHandler”>
<property name=”name” value=”client handler”/>
</handler>
</chain>
</handlerChains></wsdl></configuration>
·編寫靜態客戶
C.建立服務端處理器
·編寫服務端處理器(與客戶端結構類似)
Public boolean handleRequest(MessageContext context){
SOAPMessageContext smc=(SOAPMessageContext)context;
……
Iterator it=header.examineAllHeaderElements();
While(it.hasNext()){
SOAPElement element=(SOAPElement)it.next();
If(element name is loginfo and must understand it){
element.getValue();
element.detach();
file://Invoke only when the setMustUnderstand(true)
}
}
}
detach方法用來移除元素,這個需求僅當一個元素設置了mustUnderstand屬性在必要。
·編輯jaxrpc-ri.xml文件
<?xml version=”1.0” encoding=”UTF-8”?>
<webServices xmlns=”http://java.sun.com/jax-rpc/config/ri/dd”
version=”1.0”
targetNamespaceBase=”urn:Star:wsdl”
typeNamespaceBase=”urn:Star:types”
urlPatternBase=”/handler”>
<endpoint name=”HandlerTest”
displayName=”Handler Test”
description=” … …”
interface=”service.HandlerTest”
model=”/WEB-INF/model.gz”
implementation=”service.HandlerTestImpl”>
<handlerChains>
<chain runAt=”server”>
<handler className=”service.LoggerHandler”
headers=”ns1:loginfo”
xmlns:ns1=”urn:Star:headerprops”>
<property name=”name” value=”Logger”/>
</handler>
<handler className=”service.NameHandler”>
<propery name=”name” value=”Name”/>
</handler>
</chain>
</handlerChains>
</endpoint>
<endpointMapping endpointName=”HandlerTest”
urlPattern=”/handler”/>
</webServices>
在第一個處理器中,XML使用了屬性 headers描述頭信息。這是因為客戶代碼告訴服務端,logger頭必須被理解,否則客戶將收到SOAP錯誤消息
·生成WAR文件并部署到服務器上
4.源代碼
·HelloIF.java(endpoint接口)
package helloservice;
import java.rmi.RemoteException;
import java.rmi.Remote;
public interface HelloIF extends Remote{
public String sayHello(String target) throws RemoteException;
}
·HelloImpl.java
package helloservice;
public class HelloImpl implements HelloIF{
private String message="Hello";
public String sayHello(String target){
return message+target;
}
}
·StaticClient.java
package staticstub;
import javax.xml.rpc.Stub;
public class StaticClient{
private static String endpointAddress;
public static void main(String [] args){
if(args.length!=1){
System.err.println("Usage : java HelloClient [endpoint address]");
System.exit(-1);
}
endpointAddress=args[0];
System.out.println("Connect to :"+endpointAddress);
try{
Stub stub=createStub();
stub._setProperty(javax.xml.rpc.Stub.ENDPOINT_ADDRESS_PROPERTY,
endpointAddress);
HelloIF hello=(HelloIF)stub;
System.out.println(hello.sayHello(" Starchu!"));
}catch(Exception e){System.err.println(e.toString());}
}
private static Stub createStub(){
return (Stub)(new HelloService_Impl().getHelloIFPort());
}
}
·DynamicClient.java
package dynamicproxy;
import java.net.URL;
import javax.xml.namespace.QName;
import javax.xml.rpc.Service;
import javax.xml.rpc.ServiceFactory;
import javax.xml.rpc.JAXRPCException;
import staticstub.HelloIF;
public class DynamicClient{
private static String wsdl;
private static String namespaceUri="urn:Star:wsdl";
private static String serviceName="HandlerService";
private static String portName="HandlerTestPort";
public static void main(String [] args){
if(args.length!=1){
System.err.println("Usage : java DynamicClient [server Url]");
System.exit(-1);
}
System.out.println("Connect to :"+args[0]);
helloWsdl=args[0]+"?WSDL";
try{
URL wsdlUrl=new URL(wsdl);
ServiceFactory serviceFactory=ServiceFactory.newInstance();
Service service=
serviceFactory.createService(wsdlUrl,
new QName(namespaceUri,serviceName));
HandlerTest proxy=(HandlerTest)service.getPort(
new QName(namespaceUri,portName),HandlerTest.class);
proxy.test();
}catch(Exception e){
System.err.println(e.toString());
}
}
}
·DIIClient.java
package dii;
import javax.xml.rpc.*;
import javax.xml.namespace.*;
public class DIIClient{
private static String qnameService = "HelloService";
private static String qnamePort = "HelloIF";
private static String BODY_NAMESPACE_VALUE ="urn:Star";
private static String ENCODING_STYLE_PROPERTY ="javax.xml.rpc.encodingstyle.namespace.uri";
private static String NS_XSD ="http://www.w3.org/2001/XMLSchema";
private static String URI_ENCODING ="http://schemas.xmlsoap.org/soap/encoding/";
public static void main(String [] args){
try{
ServiceFactory factory=ServiceFactory.newInstance();
Service service=factory.createService(new QName(qnameService));
QName port=new QName(qnamePort);
Call call=service.createCall(port);
call.setTargetEndpointAddress(args[0]);
call.setProperty(Call.SOAPACTION_USE_PROPERTY,new Boolean(true));
call.setProperty(Call.SOAPACTION_URI_PROPERTY,"");
call.setProperty(ENCODING_STYLE_PROPERTY,URI_ENCODING);
QName qnameTypeString=new QName(NS_XSD,"string");
call.setReturnType(qnameTypeString);
call.setOperationName(new QName(BODY_NAMESPACE_VALUE,"sayHello"));
call.addParameter("String_1",qnameTypeString,ParameterMode.IN);
String [] params = { "Starchu" };
System.out.println((String)call.invoke(params));
}catch(Exception e){
System.err.println(e.toString());
}
}
}
·Ant文件build.xml
<project name="helloWS" basedir="." default="deploy">
<property file="build.properties"/>
<property name="build" value="build"/>
<property name="dist" value="${build}\classes"/>
<property name="lib" value="${build}\lib"/>
<property name="src" value="src"/>
<property name="etc" value="${src}\etc"/>
<target name="clean">
<delete dir="${build}"/>
</target>
<target name="init">
<mkdir dir="${build}"/>
<mkdir dir="${dist}"/>
<mkdir dir="${lib}"/>
</target>
<path id="classpath">
<fileset dir="${tomcat.home}">
<include name="jaxrpc/**/*.jar"/>
<include name="jaxb/**/*.jar"/>
<include name="jaxp/**/*.jar"/>
<include name="saaj/**/*.jar"/>
<include name="jwsdp-shared/lib/**/*.jar"/>
</fileset>
<pathelement path="${dist}"/>
<pathelement location="${lib}"/>
</path>
<target name="compile-service" depends="init">
<javac srcdir="${src}" destdir="${dist}" includes="HelloService/**/*.java"/>
</target>
<target name="generate-sei-service" depends="compile-service">
<exec executable="wscompile.bat">
<arg line="-define -d ${build} -nd ${build} -classpath ${dist} ${etc}\config-interface.xml -model ${build}\model.gz"/>
</exec>
<copy todir="${build}">
<fileset dir="${etc}" includes="*.xml"/>
</copy>
</target>
<target name="package-service" depends="generate-sei-service">
<war warfile="${build}\${ant.project.name}-portable.war"
webxml="${build}\web.xml">
<webinf dir="${build}" includes="*.xml,*.gz,*.wsdl" excludes="web.xml"/>
<classes dir="${dist}" includes="**/*.class" defaultexcludes="no"/>
</war>
</target>
<target name="process-war" depends="package-service">
<exec executable="wsdeploy.bat">
<arg line="-o ${ant.project.name}.war ${build}\${ant.project.name}-portable.war"/>
</exec>
</target>
<target name="deploy">
<copy file="${ant.project.name}.war" todir="${server}"/>
</target>
<target name="undeploy">
<delete file="${server}\${ant.project.name}.war"/>
</target>
<!-- Generating Static Client -->
<target name="generate-stubs" depends="init">
<exec executable="wscompile.bat">
<arg line="-gen:client -d ${dist} -classpath ${dist} ${etc}\config-wsdl.xml"/>
</exec>
</target>
<target name="compile-client" depends="generate-stubs">
<javac srcdir="${src}" destdir="${dist}" includes="StaticStub/**/*.java">
<classpath refid="classpath"/>
</javac>
</target>
<target name="package-client" depends="compile-client">
<jar destfile="${lib}\client.jar" basedir="${dist}" excludes="HelloService/**/*.class"/>
</target>
<target name="run-client" depends="package-client">
<java classname="staticstub.HelloClient"
classpathref="classpath"
fork="true">
<sysproperty key="endpoint" value="${endpoint}"/>
<arg value="${server.port.url}"/>
</java>
</target>
<!-- Generating Dynamic Client -->
<target name="generate-interface" depends="init">
<exec executable="wscompile.bat">
<arg line="-import -d ${dist} -classpath ${dist} ${etc}\config-wsdl.xml"/>
</exec>
</target>
<target name="compile-dynamic-client" depends="generate-interface">
<javac srcdir="${src}" destdir="${dist}" includes="DynamicProxy/**/*.java">
<classpath refid="classpath"/>
</javac>
</target>
<target name="package-dynamic-client" depends="compile-dynamic-client">
<jar destfile="${lib}\client.jar" basedir="${dist}" includes="**/HelloIF.class,**/DynamicClient.class"/>
</target>
<target name="run-dynamic-client" depends="package-dynamic-client">
<java classname="dynamicproxy.DynamicClient"
classpathref="classpath"
fork="true">
<sysproperty key="endpoint" value="${endpoint}"/>
<arg value="${server.port.url}"/>
</java>
</target>
<!-- Generating Dynamic Invocation Interface -->
<target name="compile-dii">
<javac srcdir="${src}" destdir="${dist}" includes="DII/**/*.java">
<classpath refid="classpath"/>
</javac>
</target>
<target name="run-dii" depends="compile-dii">
<java classname="dii.DIIClient"
classpathref="classpath"
fork="true">
<sysproperty key="endpoint" value="${endpoint}"/>
<arg value="${server.port.url}"/>
</java>
</target>
</project>
·屬性文件(build.xml文件使用)
server=C:/Java/jwsdp-1.2/webapps
tomcat.home=C:/Java/jwsdp-1.2
endpoint=http://localhost:8080/helloWS/hello
server.port.url=http://localhost:8080/helloWS/hello
參考資料
1. Developing Web Service Series <http://www.theserverside.com/resources/article.jsp?l=Systinet-web-services-part-1> www.theserverside.com
2. JWSDP-1.2 Tutorial java.sun.com
談到職業生涯,最基礎的是要看一個人和環境之間的適應性。現代的職業觀當然不再局限于一個工作,而是找到那些能讓自己發揮能力、技術、能表達自己想法、能在某一方面承擔某一角色的環境;而對環境的適應也是因性格的不同而相異的。
一、傳統型:這種個性類型的人在事務性的職業中最為常見。這一類人容易組織起來,喜歡和數據型及數字型的事實打交道,喜歡明確的目標,不能接受模棱兩可的狀態。這些人可以用這一類的詞語來表述他們:服從的,有秩序的,有效率的,實際的。如果用不太客氣的話說,就是缺乏想像,能自我控制,無靈活性。出納員就是這種類型的典型代表。
二、藝術型:這種類型與傳統型形成最強烈的反差。他們喜歡選擇音樂、藝術、文學、戲劇等方面的職業。他們認為自己富有想像力,直覺強,易沖動,好內省,有主見。這一類型的人語言方面的資質強于數學方面。如果用消極一些的語言描述,這類人是感情極豐富的、無組織紀律的。
三、現實主義型:這種類型的人真誠坦率,較穩定,講求實利,害羞,缺乏洞察力,容易服從。他們一般具有機械方面的能力,樂于從事半技術性的或手工性的職業(如管道工、裝配線工人等),這類職業的特點是有連續性的任務需要卻很少有社會性的需求,如談判和說服他人等。
四、社會型:社會型的人與現實主義型的人幾乎是相反的兩類。這類型喜歡為他人提供信息,幫助他人,喜歡在秩序井然、制度化的工作環境中發展人際關系和工作。這些人除了愛社交之外,還有機智老練、友好、易了解、樂于助人等特點。其個性中較消極的一面是獨斷專行,愛操縱別人。社會型的人適于從事護理、教學、市場營銷、銷售、培訓與開發等工作。
五、創新型(企業家型):這種類型的人與社會型的人相似之處在于他(她)也喜歡與人合作。其主要的區別是創新型的人喜歡領導和控制他人(而不是區幫助他人),其目的是為了達到特定的組織目標。這種類型的人自信,有雄心,精力充沛,健談。其個性特點中較消極的一面是專橫,權力欲過強,易于沖動。
六、調查研究型:這種類型與創新型幾乎相反。這一類型的人為了知識的開發與理解而樂于從事現象的觀察與分析工作。這些人思維復雜,有創見,有主見,但無紀律性,不切實際,易于沖動。生物學家、社會學家、數學家多屬于這種類型。在商業性組織中,這類人經常擔任的是研究與開發職務及咨詢參謀之職。這些職務需要的是復雜的分析,而不必去說服取信于他人。
當然,一個人往往不是單一地表現某種類型,常常是兩三種類型的組合,但不管怎樣,總要往積極的性格方向發展,要讓自己選擇工作,而不是工作選擇自己。
醫師:治病+保健最賺錢醫生從事的是高科技和高
風險的職業,
人才培養周期也大于其它行業,因此,做醫師自然越做越值錢。
律師:向專業領域發展
律師的工作性質類似于醫生,屬于經驗行業。律師今后的發展方向應努力朝某一個專業領域發展,把握該領域的發展趨勢,成為某一方面的專家里手。尤其是商業律師、企業法律顧問等專業方向相對容易獲得高薪。
教師:應用型+方法論吃香
在許多大中以上城市,教師的收入水平已經超過一般白領。今后,應用型的教師將更容易獲得高薪,因此,教師也必須經常走進企業,不斷積累“實戰”經驗,以此豐富自己的知識結構。而中小學教師面對的是有升學壓力的學生,故而傳授“方法”顯得極其重要。
古玩鑒定師:等于“古董”職業
古玩鑒定行業的門檻較高,需要具備相當深厚的中國傳統文化知識,而這些知識通過突擊不能獲得,因此,稱該職業為“越老越值錢”的“古董”職業一點也不為過……
精算師:年薪起步價20萬
普通和初級財務人員目前供大于求,但高端財務人才卻千金難覓。如果想在這個行業發展,考取相應的資格證書只是第一步,豐富的經驗能賦予你高效的管理能力、處理突發事件的應急能力。
建筑設計師:稀缺人才
屬于稀缺性人才,目前市場上對建筑設計人才大多要求5年以上的工作經驗,有多年的研究和實踐經驗的支持,薪水一定能層層攀高。
咨詢顧問:新興行業
咨詢顧問的工作就是替企業診斷“疑難雜癥”,并開出“藥方”,要用最前沿的眼光為企業做出最具戰略意義的分析。
無論你正值豆蔻年華,還是濃情綻放,抑或花期已過,都應該做些胸部保養,因為它是女性的第二張臉。先檢查一下這張臉是否足夠結實、挺拔、渾圓?皮膚是否柔軟細膩有彈性?是否因產后哺乳導致胸部下垂變形?是否因減肥不當導致胸部消瘦縮小?是否因為工作壓力大造成胸部皮膚彈性張力減低而松弛、下垂
、變形?是否因年齡關系體內激素分泌減少產生萎縮?總之,無論是先天發育不佳,抑或健康、壓力、產后哺乳,更年期等原因造成的不良影響,都會危及到你的玲瓏美麗。
紫穎倡導由內而外的自然美麗。采用法國天使豐姿技法與點穴原理創新結合的自然豐胸技法,效果看得見,已為眾多女性帶來完美曲線。結合傳統循經點穴手法,紫穎將這一西方經典的美胸技術融入傳統的點穴豐胸之中,由專家指點操作于背部、胸部,疏通經絡,加之理性互動按摩,促進血液循環;進一步使胸大肌強健,提高乳房基礎質量;促進胸部脂肪細胞吸收更多營養,同時加大脂肪細胞體積;從而達到理想的豐胸目的。更多精彩內容可上WWW.ZIYING.COM.CN。
每年6月是報關員資格全國統一考試的報名時間,那么報關員工作容易找嗎?報關員是“金領職業”嗎?什么樣的人適合從事這一行?
帶著這些問題,我們走訪了上海報關協會培訓部負責人季岳生先生和長發國際貨運有限公司空運部陶偉明先生,從行業管理和企業用人的角度,他們給想要入行的人士提供了有價值的建議。
【考試】
門檻不高過關率低
隨著我國外貿進出口量的不斷擴張,報關員考試的熱度也不斷在上升。從上海地區報名情況看,2001年有1.1萬多人報名,到2003年,人數已經上升到2萬多人。
季岳生認為,單從報名的熱度上評價這個行業的熱度有一定片面性。可以看到,接連幾年上升的報名人數,一方面與外貿行業的人才需求息息相關,另一方面,也與目前不容樂觀的大中專畢業生就業形勢相映襯,在增加的報名人數中,大專生、“三校生”占到了很大比例。
同時,每年報關員考試的過關率并不高,全國平均僅在10%左右,即使是直接從上海海關教育培訓中心出來的考生,能一次通過考試的也只有23%。報關員考試最大的難度在“報關實務”部分,這個內容占到考試內容的60%,很多沒有實際操作經驗的人往往在這上面丟分。
不過,也有人指出,以中專及高中學歷為界的報名門檻使得考生隊伍平均素質不高,報關員考試通過率較低也是在所難免的,有志于此者不必讓通過率嚇倒了。
【求職】
證書為經驗錦上添花
由于報關員考試報名熱度不斷升溫,報關員考試培訓也成了“香餑餑”,不少培訓機構表示,只要考到證,工作就一片光明。
對此,季岳生先生認為應該客觀地來看待報關員證書的分量。一方面,據他的了解,目前在上海,擁有報關員證書而找不到工作的人幾乎沒有,他表示:“如果有找不到工作的,可以到報關協會來聯系,我們可以幫他們與企業牽個線。”但另一方面,季先生承認,報關員證書能帶來的職業前景并非像一些培訓機構所說的如此光明,畢竟這個市場容量是有限度的,特別是隨著電子化報關時代的到來,技術升級會降低人工的需求,而對個人技能的要求越來越高。
作為用人單位,長發國際貨運有限公司空運部陶偉明先生把報關員證看得更淡。他表示在招聘中,工作經驗才是最關鍵的因素。像他們招聘的空運報關員,更是強調經驗,因為航班可不等人,萬一有一個環節弄錯了,就很難彌補了。如果是新手,多半只能“跟著別人跑”,一些審單工作,往往需要有三五年工作積累的報關員。
【工作】
薪資不均“金飯碗”太沉
社會上流傳著“報關員是個金飯碗”的說法,不過,業內人士同時指出,這個說法有點偏頗。
陶先生表示,報關員的工作相當辛苦,由于海關實行的是“5+2”通關,天天不關門,報關員必須跟著市場走,沒有固定的休息日,加班是常事。另外,和白領比,報關員工作環境并不理想。通關口岸往往人聲鼎沸,而通關手續又繁雜,長期從事這個工作,耐壓能力和心態調整非常重要。
季先生從薪資角度提出疑議。據他透露,報關員薪資差別非常大。一般來說,跑單的,月薪1000元左右很正常,而負責制單的,月薪能到三四千元。經驗越多,薪水還會往上走。
另外,自理報關員的收入與企業的效益相關,報關員所在的外貿企業效益好,報關員的收入也不低,很多高薪的說法大多從這些公司傳出來;比較而言,代理報關公司的薪水就較難保證,代理公司競爭激烈,相互間壓價厲害,薪資反而不如自理報關員。
季先生也坦言,在就業難背景下,這個行業確實在迅速擴張中,因此,短期之間,報關員職業的熱度不會退。
優秀報關員基本條件
1.熟悉外貿知識,了解產品屬性和貿易流程,正確填寫報關單。
2.熟悉海關各類政策、條款及相關流程,及時了解海關政策調整。
3.要具有出色的人際溝通能力,這樣在辦理通關手續時,能清楚回答海關問題,解釋疑惑。
4.工作耐心細致,把準備工作做在前面,避免返工,加速通關。
5.良好的廉政意識,切忌對海關工作人員行賄。
面對電腦時間長了不好,那該怎么辦?其實每天四杯茶,不但可以對抗輻射的侵害,還可以保護眼睛。
1、上午一杯綠茶:綠茶中含強效的抗氧化劑以及維生素C,不但可以清除體內的自由基,還能分泌出對抗緊張壓力的激素。綠茶中所含的少量咖啡因可以刺激中樞神經,振奮精神。不過最好在白天飲用,以免影響睡眠。
2、下午一杯菊花茶:菊花有明目清肝的作用,有些人就干脆用菊花加上枸杞一起泡來喝,或是在菊花茶中加入蜂蜜,都對解郁有幫助。
3、疲勞了一杯枸杞茶:枸杞子含有豐富的β胡蘿卜素、維生素B1、維生素C、鈣、鐵,具有補肝、益腎、明目的作用。其本身具有甜味,可以泡茶也可以像葡萄干一樣作零食,對解決電腦族眼睛澀、疲勞都有功效。
4、晚間一杯決明茶:決明子有清熱、明目、補腦髓、鎮肝氣、益筋骨的作用。
喜慶祥和的2007“金豬”年剛至,新華保險隆重推出一款集快速見利、祝壽養老、高額保障、分紅增值等多重功能于一體、特別適合在春節期間銷售的拳頭產品。該產品充分吸收了目前市場上熱銷的快速返還型產品特色,保險期間至被保險人88周歲保單生效對應日。產品的交費期限分10年交、20年交兩種,其中10年交投保年齡為30天以上-50周歲,20年交投保年齡為30天以上-45周歲。
富貴人生兩全保險(A款)(分紅型)產品具有快速見利、六六祝壽、年金養老、高額保障、分紅增值和特別豁免六大特點。分別是:
快速見利:66周歲前每兩年領取有效保額的8%,補充日常開銷。
六六祝壽:66周歲領取100%有效保額作為祝壽金,實現晚年心愿。
年金養老:66周歲開始每年領取有效保額的8%,以年金形式補充養老基金。
高額保障:66周歲前300%有效保額的身價保障;隨著家庭責任的減輕,66周歲開始還擁有150%有效保額的身價保障。
分紅增值:通過保額分紅,復利遞增,使領取金和身價保障不斷提高,保值增值。
特別豁免:全殘免交續期保費,繼續享受所有保障利益。
富貴人生兩全保險(A款)(分紅型)產品到底能給客戶帶來什么利益呢?筆者專程走訪了新華保險公司產品專家,據其介紹,該產品設計科學、合理,可解決客戶的零用、保障、養老和人生規劃四大需求。分別為:1、零用。提供零花錢。66周歲前有效保額8%的領取可補貼日常開支,或實現固定的小額開銷規劃,如門診醫療金、年度專項體檢金、家庭旅游金等。2、保障。提供高額保障。66周歲前300%有效保額的身價保障,66周歲開始150%有效保額的身價保障,抵御未知風險。3、養老。66周歲祝壽金和以后每年有效保額8%的年金領取,補充養老金。4、人生規劃。以現有資金,規劃未來不同階段的生活,抵御各種未知風險。
該理財專家還介紹了富貴人生產品與眾不同的五大優勢:一是返還基礎不同。“富貴人生”是以有效保額作為返還基礎,可有效緩解通貨膨脹壓力。二是返還頻率和額度不同。“富貴人生”設計更科學合理。 66周歲前每兩年返有效保額的8%,既解決客戶的部分現金領取需求,又不會對賬戶的長期收益造成很大影響;66周歲后每年返有效保額的8%,有效補充養老金。三是祝壽金設計不同。“富貴人生”領取適時,設計切合客戶退休后規劃生活的需求,66周歲返還100%有效保險金額,為客戶開始一段新的人生提供資金支持。四是身故保障的設計不同。“富貴人生”66周歲前300%有效保額的身價保障,66周歲開始150%有效保額的保障,切合客戶不同階段的人生責任。五是獨具保費豁免功能。“富貴人生”設計更加人性化,特別增加了全殘豁免保費的功能,面臨人生變故,無須繼續交費,仍然可以享受全部保險利益。
據理財專家介紹,當前百姓投資渠道狹窄,房地產投資風險加劇,股市及基金專業性強,投資市場正處于不太明朗、潛藏風險的狀況之下,新華保險《富貴人生兩全保險(A款)(分紅型)》的面世為廣大市民理財提供了一個新的選擇。
同樣是分紅險,保額、保費差不多,分到的紅利卻相差十余倍?目前,在加息預期下,分紅型保險成為大熱險種,但其實,目前,市場上的分紅型保險產品雖然同樣名曰“分紅險”,可實質上卻使用兩種不同的分紅方式,一種是現金分紅,一種是保額分紅,市民在選擇分紅險的時候,要結合自己的實際情況進行挑選。
兩種分紅方式
美式分紅:即通常所說的現金分紅,中國人壽(行情論壇)、平安保險(行情論壇)、友邦保險等大多數保險公司均采用美式分紅。
英式分紅:即通常所說的保額分紅,新華人壽、信誠人壽、太平人壽的一些產品則采用英式分紅。這兩款產品的分紅之所以差異如此之大,就在于它們根本采取了不同的分紅方式。
案例
保費相近 紅利相差數百元
市民趙先生去年投保了一份分紅型壽險,分20年交保費,年繳保費3600元,保險金額10萬元,最新接到的一份保險公司的分紅通知讓趙先生不禁大為欣喜,今年的分紅有900多元,按照自己3600元的投入,相當于一年的收益率超過了20%!保險的回報率竟然有這么高?
可在趙先生印象中,自己的朋友小唐同樣是去年投保了一款分紅型壽險,保險金額10萬元,一年要繳保費3000多元,拿到的分紅也就只有幾十元而已。
同樣是分紅型保險,為什么紅利的差距如此懸殊?趙先生頗費思量。
兩種分紅方式的計算
以投保人同樣買10萬元保額分紅險為例,20年繳,年保費3000元,假設分紅率均為1%。
若采用美式分紅,保險公司以現金價值的多少為依據計算分紅,并以現金的形式派發當年分紅。粗略來看,第一年的年終分紅為3000×1%=30元;第二年為3000×2×1%=60元,以此類推。
而若采取英式分紅,保險公司根據保額計算分紅,并將當年分紅自動累加到保戶的保額上,將紅利變成保額,復利遞增。
舉例來看,年終分紅可以這樣計算,假定每年分紅率為1%,由于保險金額為10萬元,第一年的年度紅利為10萬元×1%=1000元;第二年的年度紅利為(10萬元+1000元)×1%=1010元;而第三年的年度紅利為(10萬元+1000元+1010元)×1%=1020.1元,以此類推。
現金分紅 PK 保額分紅
收益性
保額分紅短期收益有優勢
從計算紅利的基數來看,現金分紅的計算基數為“保單的現金價值”,保額分紅的計算基數是保險金額,由于投保初期保單的現金價值比較小,因此,現金分紅險投保初期現金紅利很小,沒有保額分紅的優勢。但從長期看,隨著所交保費越來越多,與保額分紅的差異會逐步縮小。
從“分紅率”來看,由于現金分紅,每年派發現金紅利需要較高流動性,可能會制約保險公司投資收益的空間,而保額分紅派發的紅利會直接增加到保額上,這樣保險公司可以增加長期資產的投資比例,在某種程度上可增加投資收益,使被保險人能保持較高且穩定的投資收益率。
保障性
保額分紅保障性較高
從保障的角度來看,保額分紅的優勢則比較明顯。
保額分紅是以保額為基礎進行分紅,同時,會將當期紅利增加到保單的現有保額之上。這就相當于使投保人在保障期內,無需核保和申請增加保額,保額自動增加,可在一定程度上緩解因通脹導致的保障貶值。
靈活性
現金分紅可留也可取
保額分紅的紅利領取不如現金分紅靈活。
以趙先生和小唐的例子來看,第一年的分紅,趙先生所獲得的紅利是900多元,而小唐的分紅只有幾十元,但由于這筆錢轉成了保額,趙先生實際上是無法從保險公司拿到這筆錢的,只能在發生保險事故、保險期滿或退保時,才能拿回所分配的紅利。
小唐所能獲得的紅利看起來不多,但采取現金分紅的方式,紅利性質比較靈活,可留也可取,只要小唐想領取現金紅利,就可以拿到這筆分紅。而目前現金分紅險可提供累計生息、抵交保費、購買繳清增額等方式。
建議
流動性要求高選現金分紅
如果市民不急于將紅利取出,可選擇保額分紅產品,長期將獲得更高的保障,并有望獲得更好的收益。但如市民對現金的流動性要求較高,則可選擇現金分紅產品,靈活性更高。
此外,不管是“現金分紅”還是“保額分紅”,能否給投保人帶來更多的利益,關鍵還在于保險公司的運營情況。如果保險公司的資金運營能取得更高的收益率,分紅自然也會水漲船高,市民不要被分紅方式這種表面上的差別所迷惑,而更應該關注所投保的保險公司的運營能力。
- 憑出生日期的數字測出內在性格和潛在力量的測驗,看似不可思議,但近來在日本十分流行,而且被測者都發現能找到準確而奇妙的答案,不信?玩過就知。
玩法:請從你出生年、月、日中,找出不同的數字。例如:1978年12月9日,就有(2個1)(1個2)(1個7)(1個8)和(2個9),然后請參閱下文,就會知道你的感情表達能力、思維能力、失戀治療能力等內在玄妙了。
分析:
(1)代表感情表達能力
(1個1):你屬于固執而不懂表達感情的人,故經常暗戀人。由于你都算理智,甚少被情所傷。
(2個1):你善于表達感情,面對心儀對象,往往能大膽示愛。由于你喜怒形于色,戀愛過程亦見順利。
(3個1):你不易透露心底秘密,往往經過深思熟慮,才會將事情告知他人,所以做你的情人要有十足耐性。
(4個1):你十分敏感,情緒起伏不定,毫不掩飾自己的喜怒哀樂,容易意志消沉,需要情人不時地鼓勵。
(5個1):你極度情緒化,容易傷害別人,作為你的情人,一定要對你耐心關懷,才能彼此溝通無阻。
(2)代表直覺度
(1個2):你懂得顧及別人的感受,善于洞悉別人的想法,是一個可靠的朋友和情人。
(2個2):你善解人意,樂于助人,愛付出。對于異性來說,你的細心體貼甚具吸引力。
(3個2或以上):你的直覺一般,幸好反應能力強。你喜歡多姿多彩的生活,對神秘的愛情也心向往之。
(3)代表思維能力和想象力
(1個3):超強的想象力令你能散發獨特的魅力,而你同時向往浪漫而甜蜜的戀愛。
(2個3):言行常超出常規,常胡思亂想。與愛侶相處時,經常云游太虛,令對方覺得無趣。
(3個3或以上):你智商很高,思維清晰,無法忍受單調的生活,若沒機會發揮才能,會變得精神緊張。
(4)代表行動力
(1個4):熱情澎湃,言出必行,自信十足,你會大膽表露內心感情,性欲亦旺盛。
(2個4或以上):做人缺乏自信心,對于愛侶忠心耿耿。不會有越軌的念頭,亦期望另一半對你專心不二。
(5)代表意志堅定度
(1個5):思想單純,即使情人見異思遷,你亦不會放棄,希望有守得云開見月明的一日。
(2個5):你的意志并不堅定,容易半途而廢,往往事倍功半,想好好發揮才能,最好將精力放在創意活動上。
(3個5或以上):內心有著無法克制的熱情,做事沖動,決不會改變自己決定的事情,還要另一半聽你指示。
(6)代表自我價值
(1個6):你天性敏感,喜歡被別人欣賞,只有這樣,你才能感受到自己存在的價值。
(2個6):你多愁善感,缺乏自信,伴侶對你的愛護,是你發揮才能的推動力。
(3個6或以上):你有絕對的自信心,為了令自己與眾不同,永遠全力以赴,喜歡出風頭。
(7)代表失戀治療能力
(1個7):談戀愛時,你會為對方周全考慮,失戀治療能力亦強,對人歡笑,背人垂淚。
(2個7):由于你每次戀愛都很投入,故失戀時,往往傷得很深,需要向別人傾訴,才能解開郁結。
(3個7或以上):你不易愛上人,但一旦戀愛,會是十分專情的情人。如果被拋棄,你會對曾經的一切念念不忘。
(8)代表智力和邏輯性
(1個8):你智力一般,但邏輯性強,做事喜歡循序漸進,不喜歡預期以外的變化。
(2個8或以上):你聰明獨立,表達能力強,有決斷能力,有領導才華,做事往往得心應手。
(9)代表體貼度
(1個9):你嘗試理解別人對感情的渴求,然后盡量配合。
(2個9或以上):無論智力或精力,你都非常旺盛,但缺點是經常沉醉于自己的想法中,令情人覺得你難以捉摸。
(10)代表精神力量
(1個0):你一生的時間和精神力量,均被情人及朋友瓜分,緊記要在友情和愛情之間找出一個平衡點。
(2個0或以上):你的自我精神極強,在逆境時往往能看出事情的真相,甚少作出錯誤決定