c#Winform程序調用app.config文件配置數據庫鏈接字符串 SQL Server文章目錄 淺談SQL Server中統計對於查詢的影響 有關索引的DMV SQL Server中的執行引擎入

你新建winform項目的時候,會有一個app.config的配置文件,寫在裏面的<connectionStrings name="  " connectionString="  "></connectionStrings >,html

connectionString表明數據庫連接字符串,name表明你想要引用的時候查找的名稱。(其實asp裏的web.config配置方式也跟這個方式基本同樣)程序員

1.打開app.config配置文件web

例如:算法

複製代碼
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
  </startup>

    <connectionStrings>
      <add name ="TestDB" connectionString="Data Source=.;Initial Catalog=學生課表管理系統;Persist Security Info=True;User ID=;Password= "/>
    </connectionStrings>

</configuration>
複製代碼

2.在c#後臺代碼調用這個數據鏈接字符串配置文件sql

完成以上配置,在後臺代碼要先引入using System.Configuration;數據庫

 在你須要調用數據庫的地方加入如下代碼編程

複製代碼
 string Stu = ConfigurationManager.ConnectionStrings["TestDB"].ConnectionString;    
//TestDB這裏是你剛纔配置文件裏鏈接的NAME
            SqlConnection conn = new SqlConnection(Stu); //鏈接數據庫

            if (conn.State == ConnectionState.Closed)
            {
                conn.Open();
            } //判斷若是數據庫關閉,就打開數據庫
複製代碼

 

以上就是在app.config配置文件裏配置鏈接字符串的方式c#

 

走近我,靠近我,

 

 
 

SQL Server文章目錄

 

SQL Server的文章寫了也很多了,一直沒有作一個目錄方便你們閱讀。如今把以前寫的關於SQL Server的文章作一個目錄,方便你們閱讀 眨眼windows

SQL入門

SQL查詢入門(上篇)緩存

SQL查詢入門(中篇)

SQL查詢入門(下篇)

 

SQL進階

T-SQL查詢進階--深刻理解子查詢

T-SQL查詢進階--基於列的邏輯表達式

T-SQL查詢進階--流程控制語句

T-SQL查詢進階--變量

T-SQL查詢進階--數據集之間的運算

T-SQL查詢進階-10分鐘理解遊標

T-SQL查詢進階--深刻淺出視圖

T-SQL查詢進階--詳解公用表表達式(CTE)

T-SQL查詢進階--理解SQL Server中索引的概念,原理以及其餘

T-SQL查詢高級--理解SQL SERVER中非彙集索引的覆蓋,鏈接,交叉和過濾

T-SQL查詢進階--理解SQL SERVER中的分區表

T-SQL查詢高級—SQL Server索引中的碎片和填充因子

T-SQL查詢進階—理解SQL Server中的鎖

 

SQL日誌

淺談SQL Server中的事務日誌(一)----事務日誌的物理和邏輯構架

淺談SQL Server中的事務日誌(二)----事務日誌在修改數據時的角色

淺談SQL Server中的事務日誌(三)----在簡單恢復模式下日誌的角色

淺談SQL Server中的事務日誌(四)----在完整恢復模式下日誌的角色

淺談SQL Server中的事務日誌(五)----日誌在高可用和災難恢復中的做用

SQL Server中In-Flight日誌到底是多少

再談SQL Server中日誌的的做用

 

SQL Server高可用性

SQL Server中的高可用性(1)----高可用性概覽

SQL Server中的高可用性(2)----文件與文件組

SQL Server中的高可用性(3)----複製

 

SQL的一些單篇

如何附加被分離的質疑數據庫?

數據庫集羣技術漫談

T-SQL中的GROUP BY GROUPING SETS

理解SQL SERVER中的邏輯讀,預讀和物理讀

SQL Server中數據庫文件的存放方式,文件和文件組

淺談SQL SERVER中事務的ACID

SQL Server中生成測試數據

SQL Server中災難時備份結尾日誌(Tail of log)的兩種方法

從性能的角度談SQL Server彙集索引鍵的選擇

SQL Server中的Merge關鍵字

淺談SQL Server中的快照

細說SQL Server中的加密

C#實現平衡多路查找樹(B樹)

SQL Server中的執行引擎入門

淺談SQL Server中統計對於查詢的影響

對於表列數據類型選擇的一點思考

硬盤的原理以及SQL Server如何利用硬盤原理減小IO

淺談SQL Server 對於內存的管理

DBA應該知道的一些SQL Server跟蹤標記

談一談SQL Server中的執行計劃緩存(上)

談一談SQL Server中的執行計劃緩存(下)

從範式和性能的角度談一談表的設計

SQL Server CheckPoint的幾個誤區

SQL Server數據庫損壞、檢測以及簡單的修復辦法

SQL Server中的窗口函數

 

SQL Server 2012新特性探祕

SQL Server 2012中的ColumnStore Index嘗試

SQL Server2012 T-SQL對分頁的加強嘗試

SQL Server2012中的SequenceNumber嘗試

SQL Server 2012新增的內置函數嘗試

SQL Server 2012中的Contained Database嘗試

SQL Server2012中的Throw語句嘗試

SQL Server2012中的Indirect CheckPoint

SQL Server 2012中的AlwaysOn嘗試

 

SQL Server 2014新特性探祕

SQL Server 2014新特性探祕(1)-內存數據庫

SQL Server 2014新特性探祕(2)-SSD Buffer Pool Extension

SQL Server 2014新特性探祕(3)-可更新列存儲彙集索引

SQL Server權限

理解SQL Server中的權限體系(上)----主體

理解SQL Server中的權限體系(下)----安全對象和權限

 

SQL Server複製

SQL Server複製入門(一)----複製簡介

SQL Server複製入門(二)----複製的幾種模式

複製中發佈服務器和訂閱服務器內容不一致的解決辦法

 

SQL Server一些有用的DMV

有關鎖和內存使用的DMV

有關查詢和執行計劃的DMV

有關索引的DMV

 

SQL Server索引進階(翻譯)

 

【譯】SQL Server索引進階第二篇:深刻非彙集索引

【譯】SQL Server索引進階第四篇:頁和區

【譯】SQL Server索引進階第六篇:書籤

【譯】SQL Server索引進階第八篇:惟一索引

【譯】SQL Server索引進階第十篇:索引的內部結構

【譯】SQL Server索引進階第十五篇:索引的最佳實踐

 

 

翻譯的文章

 

【譯】初識SSRS ----通向報表服務的階梯系列(一)

【譯】SSRS基礎 ----通向報表服務的階梯系列(二)

【譯】無處不在的數據 ----通向報表服務的階梯系列(三)

【譯】Tablix指南----通向報表服務的階梯系列(四)

【譯】用圖表展現未知----通向報表服務的階梯系列(五)

【譯】設計儀表盤----通向報表服務的階梯系列(六)

【譯】一些優化你的SQL語句的TIPs

【譯】RAID的概念和RAID對於SQL性能的影響

【譯】表變量和臨時表的比較

【譯】索引列,列選擇率和等式謂詞

【譯】如何使用索引視圖和一個只有2行的表限制業務規則

【譯】使用SQL生成非均勻隨機數

【譯】什麼狀況下應該分解複雜的查詢來提高性能

 

 

http://www.cnblogs.com/CareySon/archive/2012/05/08/2489748.html

 

 

 

淺談SQL Server中統計對於查詢的影響

 

簡介

    SQL Server查詢分析器是基於開銷的。一般來說,查詢分析器會根據謂詞來肯定該如何選擇高效的查詢路線,好比該選擇哪一個索引。而每次查詢分析器尋找路徑時,並不會每一次都去統計索引中包含的行數,值的範圍等,而是根據必定條件建立和更新這些信息後保存到數據庫中,這也就是所謂的統計信息。

 

如何查看統計信息

    查看SQL Server的統計信息很是簡單,使用以下指令:

    DBCC SHOW_STATISTICS('表名','索引名')

 

    所獲得的結果如圖1所示。

    1

    圖1.統計信息

 

統計信息如何影響查詢

    下面咱們經過一個簡單的例子來看統計信息是如何影響查詢分析器。我創建一個測試表,有兩個INT值的列,其中id爲自增,ref上創建非彙集索引,插入100條數據,從1到100,再插入9900條等於100的數據。圖1中的統計信息就是示例數據的統計信息。

    此時,我where後使用ref值做爲查詢條件,可是給定不一樣的值,咱們能夠看出根據統計信息,查詢分析器作出了不一樣的選擇,如圖2所示。

    3

     圖2.根據不一樣的謂詞,查詢優化器作了不一樣的選擇

 

     其實,對於查詢分析器來講,柱狀圖對於直接能夠肯定的謂詞很是管用,這些謂詞好比:

    where date = getdate() 
    where id= 12345 
    where monthly_sales < 10000 / 12 
    where name like 「Careyson」 + 「%」

 

    可是對於好比

    where price = @vari 
    where total_sales > (select sum(qty) from sales) 
    where a.id =b.ref_id

    where col1 =1 and col2=2

 

    這類在運行時才能知道值的查詢,採樣步長就明顯不是那麼好用了。另外,上面第四行若是謂詞是兩個查詢條件,使用採樣步長也並很差用。由於不管索引有多少列,採樣步長僅僅存儲索引的第一列。當柱狀圖再也不好用時,SQL Server使用密度來肯定最佳的查詢路線。

    密度的公式是:1/表中惟一值的 個數。當密度越小時,索引越容易被選中。好比圖1中的第二個表,咱們能夠經過以下公式來計算一下密度:

    4

    圖3.某一列的密度

 

    根據公式能夠推斷,當表中的數據量逐漸增大時,密度會愈來愈小。

    對於那些不能根據採樣步長作出選擇的查詢,查詢分析器使用密度來估計行數,這個公式爲:估計的行數=表中的行數*密度

    那麼,根據這個公式,若是我作查詢時,估計的行數就會爲如圖4所示的數字。

    5

    圖4.估計的行數

 

    咱們來驗證一下這個結論,如圖5所示。

    6

    圖5.估計的行數

 

    所以,能夠看出,估計的行數是和實際的行數有出入的,當數據分佈均勻時,或者數據量大時,這個偏差將會變的很是小。

 

統計信息的更新

    由上面的例子能夠看到,查詢分析器因爲依賴於統計信息進行查詢,那麼過期的統計信息則可能致使低效率的查詢。統計信息既能夠由SQL Server來進行管理,也能夠手動進行更新,也能夠由SQL Server管理更新時手動更新。

    當開啓了自動更新後,SQL Server監控表中的數據更改,當達到臨界值時則會自動更新數據。這個標準是:

  •     向空表插入數據時
  •     少於500行的表增長500行或者更多
  •     當表中行多於500行時,數據的變化量大於20%時

    上述條件的知足均會致使統計被更新。

    固然,咱們也可使用以下語句手動更新統計信息。

     

     UPDATE STATISTICS 表名[索引名]

 

列級統計信息

    SQL Server還能夠針對不屬於任何索引的列建立統計信息來幫助查詢分析器獲取」估計的行數「.當咱們開啓數據庫級別的選項「自動建立統計信息」如圖6所示。

    7

    圖6.自動建立統計信息

 

   當這個選項設置爲True時,當咱們where謂詞指定了不在任何索引上的列時,列的統計信息會被建立,可是會有如下兩種狀況例外:

  •     建立統計信息的成本超過生成查詢計劃的成本
  •     當SQL Server忙時不會自動生成統計信息

 

   咱們能夠經過系統視圖sys.stats來查看這些統計信息,如圖7所示。

    8

    圖7.經過系統視圖查看統計信息

 

    固然,也能夠經過以下語句手動建立統計信息:

    CREATE STATISTICS 統計名稱 ON 表名 (列名 [,...n])

 

總結

    本文簡單談了統計信息對於查詢路徑選擇的影響。過期的統計信息很容易形成查詢性能的下降。所以,按期更新統計信息是DBA重要的工做之一。

 

 

有關索引的DMV

 

有關索引的DMV

1.查看那些被大量更新,卻不多被使用的索引

 

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED 
SELECT                                                    
    DB_NAME() AS DatabaseName 
    , SCHEMA_NAME(o.Schema_ID) AS SchemaName 
    , OBJECT_NAME(s.[object_id]) AS TableName 
    , i.name AS IndexName 
    , s.user_updates 
    , s.system_seeks + s.system_scans + s.system_lookups 
                          AS [System usage] 
INTO #TempUnusedIndexes 
FROM   sys.dm_db_index_usage_stats s 
INNER JOIN sys.indexes i ON s.[object_id] = i.[object_id] 
    AND s.index_id = i.index_id 
INNER JOIN sys.objects o ON i.object_id = O.object_id    
WHERE 1=2 
EXEC sp_MSForEachDB 'USE [?];                           
INSERT INTO #TempUnusedIndexes 
SELECT TOP 20 
    DB_NAME() AS DatabaseName 
    , SCHEMA_NAME(o.Schema_ID) AS SchemaName 
    , OBJECT_NAME(s.[object_id]) AS TableName 
    , i.name AS IndexName 
    , s.user_updates 
    , s.system_seeks + s.system_scans + s.system_lookups 
                                         AS [System usage] 
FROM   sys.dm_db_index_usage_stats s 
INNER JOIN sys.indexes i ON s.[object_id] = i.[object_id] 
    AND s.index_id = i.index_id 
INNER JOIN sys.objects o ON i.object_id = O.object_id    
WHERE s.database_id = DB_ID() 
AND OBJECTPROPERTY(s.[object_id], ''IsMsShipped'') = 0 
AND s.user_seeks = 0 
    AND s.user_scans = 0 
    AND s.user_lookups = 0 
AND i.name IS NOT NULL 
ORDER BY s.user_updates DESC'                            
SELECT TOP 20 * FROM #TempUnusedIndexes ORDER BY [user_updates] DESC 
DROP TABLE #TempUnusedIndexes

 

結果如圖:

1

 

這類索引應該被Drop掉

 

 

最高維護代價的索引

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED 
SELECT                                                     
    DB_NAME() AS DatabaseName 
    , SCHEMA_NAME(o.Schema_ID) AS SchemaName 
    , OBJECT_NAME(s.[object_id]) AS TableName 
    , i.name AS IndexName 
    , (s.user_updates ) AS [update usage] 
    , (s.user_seeks + s.user_scans + s.user_lookups) AS [Retrieval usage] 
    , (s.user_updates) - 
      (s.user_seeks + s.user_scans + s.user_lookups) AS [Maintenance cost] 
    , s.system_seeks + s.system_scans + s.system_lookups AS [System usage] 
    , s.last_user_seek 
    , s.last_user_scan 
    , s.last_user_lookup 
INTO #TempMaintenanceCost 
FROM   sys.dm_db_index_usage_stats s 
INNER JOIN sys.indexes i ON  s.[object_id] = i.[object_id] 
    AND s.index_id = i.index_id 
INNER JOIN sys.objects o ON i.object_id = O.object_id    
WHERE 1=2 
EXEC sp_MSForEachDB 'USE [?];                              
INSERT INTO #TempMaintenanceCost 
SELECT TOP 20 
    DB_NAME() AS DatabaseName 
    , SCHEMA_NAME(o.Schema_ID) AS SchemaName 
    , OBJECT_NAME(s.[object_id]) AS TableName 
    , i.name AS IndexName 
    , (s.user_updates ) AS [update usage] 
    , (s.user_seeks + s.user_scans + s.user_lookups) 
                    AS [Retrieval usage] 
    , (s.user_updates) - 
(s.user_seeks + user_scans + 
                         s.user_lookups) AS [Maintenance cost] 
    , s.system_seeks + s.system_scans + s.system_lookups AS [System usage] 
    , s.last_user_seek 
    , s.last_user_scan 
    , s.last_user_lookup 
FROM   sys.dm_db_index_usage_stats s 
INNER JOIN sys.indexes i ON s.[object_id] = i.[object_id] 
    AND s.index_id = i.index_id 
INNER JOIN sys.objects o ON i.object_id = O.object_id    
WHERE s.database_id = DB_ID() 
    AND i.name IS NOT NULL 
    AND OBJECTPROPERTY(s.[object_id], ''IsMsShipped'') = 0 
    AND (s.user_seeks + s.user_scans + s.user_lookups) > 0 
ORDER BY [Maintenance cost] DESC'                        
SELECT top 20 * FROM #TempMaintenanceCost ORDER BY [Maintenance cost] DESC 
DROP TABLE #TempMaintenanceCost

 

結果如圖:

2

 

Maintenance cost高的應該被Drop掉

 

使用頻繁的索引

--使用頻繁的索引 
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED 
SELECT                                           
    DB_NAME() AS DatabaseName 
        , SCHEMA_NAME(o.Schema_ID) AS SchemaName 
    , OBJECT_NAME(s.[object_id]) AS TableName 
    , i.name AS IndexName 
    , (s.user_seeks + s.user_scans + s.user_lookups) AS [Usage] 
    , s.user_updates 
    , i.fill_factor 
INTO #TempUsage 
FROM sys.dm_db_index_usage_stats s 
INNER JOIN sys.indexes i ON s.[object_id] = i.[object_id] 
    AND s.index_id = i.index_id 
INNER JOIN sys.objects o ON i.object_id = O.object_id    
WHERE 1=2 
EXEC sp_MSForEachDB 'USE [?];                               
INSERT INTO #TempUsage 
SELECT TOP 20 
    DB_NAME() AS DatabaseName 
    , SCHEMA_NAME(o.Schema_ID) AS SchemaName 
    , OBJECT_NAME(s.[object_id]) AS TableName 
    , i.name AS IndexName 
    , (s.user_seeks + s.user_scans + s.user_lookups) AS [Usage] 
    , s.user_updates 
    , i.fill_factor 
FROM   sys.dm_db_index_usage_stats s 
INNER JOIN sys.indexes i ON s.[object_id] = i.[object_id] 
            AND s.index_id = i.index_id 
INNER JOIN sys.objects o ON i.object_id = O.object_id    
WHERE s.database_id = DB_ID() 
    AND i.name IS NOT NULL 
    AND OBJECTPROPERTY(s.[object_id], ''IsMsShipped'') = 0 
ORDER BY [Usage] DESC'                                    
SELECT TOP 20 * FROM #TempUsage ORDER BY [Usage] DESC 
DROP TABLE #TempUsage

 

結果如圖

3

 

這類索引須要格外注意,不要在優化的時候幹掉

 

 

 

碎片最多的索引


SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED 
SELECT                                                     
    DB_NAME() AS DatbaseName 
    , SCHEMA_NAME(o.Schema_ID) AS SchemaName 
    , OBJECT_NAME(s.[object_id]) AS TableName 
    , i.name AS IndexName 
    , ROUND(s.avg_fragmentation_in_percent,2) AS [Fragmentation %] 
INTO #TempFragmentation 
FROM sys.dm_db_index_physical_stats(db_id(),null, null, null, null) s 
INNER JOIN sys.indexes i ON s.[object_id] = i.[object_id] 
    AND s.index_id = i.index_id 
INNER JOIN sys.objects o ON i.object_id = O.object_id    
WHERE 1=2 
EXEC sp_MSForEachDB 'USE [?];                                
INSERT INTO #TempFragmentation 
SELECT TOP 20 
    DB_NAME() AS DatbaseName 
    , SCHEMA_NAME(o.Schema_ID) AS SchemaName 
    , OBJECT_NAME(s.[object_id]) AS TableName 
    , i.name AS IndexName 
    , ROUND(s.avg_fragmentation_in_percent,2) AS [Fragmentation %] 
FROM sys.dm_db_index_physical_stats(db_id(),null, null, null, null) s 
INNER JOIN sys.indexes i ON s.[object_id] = i.[object_id] 
    AND s.index_id = i.index_id 
INNER JOIN sys.objects o ON i.object_id = O.object_id    
WHERE s.database_id = DB_ID() 
  AND i.name IS NOT NULL 
  AND OBJECTPROPERTY(s.[object_id], ''IsMsShipped'') = 0 
ORDER BY [Fragmentation %] DESC'                          
SELECT top 20 * FROM #TempFragmentation ORDER BY [Fragmentation %] DESC 
DROP TABLE #TempFragmentation

 

結果以下:

4

 

這類索引須要Rebuild,不然會嚴重拖累數據庫性能

 

自上次SQL Server重啓後,找出徹底沒有使用的索引

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED 
SELECT                                                 
    DB_NAME() AS DatbaseName 
    , SCHEMA_NAME(O.Schema_ID) AS SchemaName 
    , OBJECT_NAME(I.object_id) AS TableName 
    , I.name AS IndexName 
INTO #TempNeverUsedIndexes 
FROM sys.indexes I INNER JOIN sys.objects O ON I.object_id = O.object_id 
WHERE 1=2 
EXEC sp_MSForEachDB 'USE [?];                           
INSERT INTO #TempNeverUsedIndexes 
SELECT 
    DB_NAME() AS DatbaseName 
    , SCHEMA_NAME(O.Schema_ID) AS SchemaName 
    , OBJECT_NAME(I.object_id) AS TableName 
    , I.NAME AS IndexName 
FROM sys.indexes I INNER JOIN sys.objects O ON I.object_id = O.object_id 
LEFT OUTER JOIN sys.dm_db_index_usage_stats S ON S.object_id = I.object_id 
        AND I.index_id = S.index_id 
        AND DATABASE_ID = DB_ID() 
WHERE OBJECTPROPERTY(O.object_id,''IsMsShipped'') = 0 
  AND I.name IS NOT NULL 
  AND S.object_id IS NULL' 
SELECT * FROM #TempNeverUsedIndexes                         
ORDER BY DatbaseName, SchemaName, TableName, IndexName 
DROP TABLE #TempNeverUsedIndexes

 

結果如圖:

5

 

這類索引應該當心對待,不能一律而論,要看是什麼緣由致使這種問題

 

查看索引統計的相關信息

 

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED 
SELECT 
    ss.name AS SchemaName 
    , st.name AS TableName 
    , s.name AS IndexName 
    , STATS_DATE(s.id,s.indid) AS 'Statistics Last Updated' 
    , s.rowcnt AS 'Row Count' 
    , s.rowmodctr AS 'Number Of Changes' 
    , CAST((CAST(s.rowmodctr AS DECIMAL(28,8))/CAST(s.rowcnt AS 
DECIMAL(28,2)) * 100.0) 
                             AS DECIMAL(28,2)) AS '% Rows Changed' 
FROM sys.sysindexes s 
INNER JOIN sys.tables st ON st.[object_id] = s.[id] 
INNER JOIN sys.schemas ss ON ss.[schema_id] = st.[schema_id] 
WHERE s.id > 100 
  AND s.indid > 0 
  AND s.rowcnt >= 500 
ORDER BY SchemaName, TableName, IndexName

 

結果以下:

6 

 

由於查詢計劃是根據統計信息來的,索引的選擇一樣取決於統計信息,因此根據統計信息更新的多寡能夠看出數據庫的大致情況,20%的自動更新對於大表來講很是慢。

 

 

SQL Server中的執行引擎入門

 

簡介

    當查詢優化器(Query Optimizer)將T-SQL語句解析後並從執行計劃中選擇最低消耗的執行計劃後,具體的執行就會交由執行引擎(Execution Engine)來進行執行。本文旨在分類講述執行計劃中每一種操做的相關信息。

 

數據訪問操做

    首先最基本的操做就是訪問數據。這既能夠經過直接訪問表,也能夠經過訪問索引來進行。表內數據的組織方式分爲堆(Heap)和B樹,其中表中沒有創建彙集索引時數據是經過堆進行組織的,這個是無序的,表中創建彙集索引後和非彙集索引的數據都是以B樹方式進行組織,這種方式數據是有序存儲的。一般來講,非彙集索引僅僅包含整個表的部分列,對於過濾索引,還僅僅包含部分行。

    除去數據的組織方式不一樣外,訪問數據也分爲兩種方式,掃描(Scan)和查找(Seek),掃描是掃描整個結構的全部數據,而查找只是查找整個結構中的部分數據。所以能夠看出,因爲堆是無序的,因此不可能在堆上面進行查找(Seek)操做,而相對於B樹的有序,使得在B樹中進行查找成爲可能。當針對一個以堆組織的表進行數據訪問時,就會進行堆掃描,如圖1所示。

    1

    圖1.表掃描

 

    能夠看出,表掃描的圖標很清晰的代表表掃描的性質,在一個無序組織表中從頭至尾掃描一遍。

    而對於B樹結構的彙集索引和非彙集索引,一樣能夠進行掃描,一般來說,爲了獲取索引表中的全部數據或是得到索引行樹佔了數據大多數使得掃描的成本小於查找時,會進行彙集索引掃描。如圖2所示。

    3

    圖2.彙集索引掃描

 

    彙集索引掃描的圖標也一樣可以清晰的代表彙集索引掃描的性質,找到最左邊的葉子節點後,依次掃描全部葉子節點,達到掃描整個結構的做用。固然對於非彙集索引也是一樣的概念,如圖3所示。

    2

    圖3.非彙集索引的掃描

 

    而對於僅僅選擇B樹結構中的部分數據,索引查找(Seek)使得B樹變得有意義。根據所查找的關鍵值,可使得從僅僅從B樹根部向下走單一路徑,所以免去了掃描沒必要要頁的消耗,圖4是查詢計劃中的一個索引查找。

    4

    圖4.彙集索引查找

 

    索引查找的圖標也是很傳神的,能夠看到圖標那根線從根節點一路向下到葉子節點。也就是找到所求數據所在的頁,不難看出,若是咱們須要查找多條數據且分散在不一樣的頁中,這個查找操做須要重複執行不少回,當這個次數大到必定程度時,SQL Server會選擇消耗比較低的索引掃描而不是再去重複索引查找。對於非彙集索引查找,概念是同樣的,就再也不上圖片了。

 

書籤查找(Bookmark Lookup)

     你也許會想,假如非彙集索引能夠快速的找到所求的數據,但遺憾的是,非彙集索引卻不包含全部所求列時該怎麼辦?這時SQL Server會面臨兩個選擇,直接訪問基本表去獲取數據或是在非彙集索引中找到數據後,再去基本表得到非彙集索引沒有覆蓋到的所求列。這個選擇取決於所估計的行數等統計信息。查詢分析器會選擇消耗比較少的那個。

    一個簡單的書籤查找如圖5所示。

    5

    圖5.一個簡單的書籤查找

 

    從圖5能夠看出,首先經過非彙集索引找到所求的行,但這個索引並不包含全部的列,所以還要額外去基本表中找到這些列,所以要進行鍵查找,若是基本表是以堆進行組織的,那麼這個鍵查找(Key Lookup)就會變成RID查找(RID Lookup),鍵查找和RID查找統稱爲書籤查找。

    不過有時候索引查找所返回的行數過多致使書籤查找的性能遠不如直接進行掃描操做,所以SQL Server這時會選擇掃描而不是書籤查找。如圖6所示。

    6

    圖6.StateProvinceID列有非彙集索引,但因爲返回行數過多,分析器會選擇掃描而不是書籤查找   

 

    這個估計是根據統計信息進行的,關於統計信息,能夠看我以前的一篇博文:淺談SQL Server中統計對於查詢的影響

 

聚合操做(Aggregation)

    聚合函數會致使聚合操做。聚合函數是將一個集合的數據按照某種規則彙總成1個數據,或基於分組按照規則彙總成多個數據的過程。一些聚合函數好比:avg,sum,min,另外還有distinct關鍵字都有可能致使兩類聚合操做:流聚合(Stream Aggregation)和哈希聚合(Hash Aggregation)。

 

流聚合(Stream Aggregation)

    流聚合須要再執行聚合函數以前,被聚合的數據集合是有序的,這個有序數據既能夠經過執行計劃中的Sort進行,也能夠直接從彙集或是非彙集索引中直接得到有序數據,另外,沒有Group by的聚合操做被成爲標量聚合,這類操做必定是會執行流聚合。

    好比,咱們直接進行標量聚合,如圖7所示。

    7

    圖7.流聚合

 

   但對於加了Group by的子句,由於須要數據按照group by 後面的列有序,就須要Sort來保證排序。注意,Sort操做是佔用內存的操做,當內存不足時還會去佔用tempdb。SQL Server老是會在Sort操做和散列匹配中選擇成本最低的。一個須要Sort的操做如圖8所示。

    8

    圖8.須要排序的流聚合

 

    圖8中排序操做按照ProductLine進行排序後,而後就根據各自的分組作聚合操做了。

 

散列聚合(Hash aggregation)

    上面的流聚合適合比較少的數據,可是對於相對大一點的表。使用散列集合成本會比排序要低。散列集合經過在內存中創建散列表來實現聚合,所以無需對數據集合進行排序。內存中所創建的散列表以Group by後面的列做爲鍵值,如圖9所示。

    9 
    圖9.散列聚合

 

    在內存中創建好散列表後,會按照group by後面的值做爲鍵,而後依次處理集合中的每條數據,當鍵在散列表中不存在時,向散列表添加條目,當鍵已經在散列表中存在時,按照規則(規則是聚合函數,好比Sum,avg什麼的)計算散列表中的值(Value)。

 

 

鏈接(Join)

    當多表鏈接時(書籤查找,索引之間的鏈接都算),SQL Server會採用三類不一樣的鏈接方式:循環嵌套鏈接(Nested Loops Join),合併鏈接(Merge Join),散列鏈接(Hash Join)。這幾種鏈接並非哪一種會比另外一種更好,而是每種鏈接方式都會適應特定場景。

 

循環嵌套鏈接(Nested Loops Join)

    由圖10能夠看到一個簡單的循環嵌套鏈接。

    10

    圖10.一個循環嵌套鏈接的實例

 

    循環嵌套鏈接的圖標一樣十分傳神,處在上面的外部輸入(Outer input),這裏也就是彙集索引掃描。和處在下面的內部輸入(Inner Input),這裏也就是彙集索引查找。外部輸入僅僅執行一次,根據外部輸入知足Join條件的每一行,對內部輸入進行查找。這裏因爲是290行,對於內部輸入執行290次。

    能夠經過屬性窗口看到.如圖11所示:

    13

    圖11.內部輸入的執行次數

 

    根據嵌套循環的原理不難看出,因爲外部輸入是掃描,內部輸入是查找,當兩個Join的表外部輸入結果集比較小,而內部輸入所查找的表很是大時,查詢優化器更傾向於選擇循環嵌套方式。

 

合併鏈接(Merge Join)

    不一樣於循環嵌套的是,合併鏈接是從每一個表僅僅執行一次訪問。從這個原理來看,合併鏈接要比循環嵌套要快了很多。下面來看一個典型的合併鏈接,如圖12所示。

    11

    圖12.合併鏈接

 

    從合併鏈接的原理不難想象,首先合併鏈接須要雙方有序.而且要求Join的條件爲等於號。由於兩個輸入條件已經有序,因此從每個輸入集合中取一行進行比較,相等的返回,不相等的捨棄,從這裏也不難看出Merge join爲何只容許Join後面是等於號。從圖11的圖標中咱們能夠看出這個原理。

    若是輸入數據的雙方無序,則查詢分析器不會選擇合併鏈接,咱們也能夠經過索引提示強制使用合併鏈接,爲了達到這一目的,執行計劃必須加上一個排序步驟來實現有序,如圖13所示。

    12

    圖13.經過排序來實現Merge Join

 

散列鏈接(Hash Join)

    散列鏈接一樣僅僅只須要只訪問1次雙方的數據。散列鏈接經過在內存中創建散列表實現。這比較消耗內存,若是內存不足還會佔用tempdb。但並不像合併鏈接那樣須要雙方有序。一個典型的散列鏈接如圖14所示。

    14

    圖14.散列鏈接

 

    這裏我刪除了Costomer的彙集索引,不然兩個有序輸入SQL Server會選擇代價更低的合併鏈接。SQL Server利用兩個上面的輸入生成哈希表,下面的輸入來探測,能夠在屬性窗口看到這些信息,如圖15所示。

    15

    圖15.散列鍵生成和散列鍵探測

 

    一般來講,在兩個輸入數據比較大,且所求數據在其中一方或雙方沒有排序的條件達成時,會選用散列匹配。

 

並行

    當多個錶鏈接時,SQL Server還容許在多CPU或多核的狀況下容許查詢並行,這樣無疑提升了效率,一個並行的例子如圖16所示。

    16

   圖16.並行提升效率

 

總結

    本文簡單介紹了SQL Server執行計劃中常見的操做極其原理,瞭解這些步驟和原理是優化查詢的基本功。

 

 

【譯】表變量和臨時表的比較

 

    關於表變量是什麼(和表變量不是什麼),以及和臨時表的比較讓不少人很是困惑。雖然網上已經有了不少關於它們的文章,但我並無發現一篇比較全面的。在本篇文章中,咱們將探索表變量和臨時表是什麼(以及不是什麼),而後咱們經過使用臨時表和表變量對其解密。

 

表變量

    表變量在SQL Server 2000中首次被引入,那麼,什麼是表變量呢?微軟在BOL (Declare @local_variable)中定義其爲一個類型爲表的變量。它的具體定義包括列定義,列名,數據類型和約束。而在表變量中可使用的約束包括主鍵約束,惟一約束,Null約束和Check約束(外鍵約束不能在表變量中使用).定義表變量的語句是和正常使用Create table定義表語句的子集。只是表變量經過DECLARE @local_variable 語句進行定義。

 

經過參考1能夠知道:

1) 表變量擁有特定做用域(在當前批處理語句中,但不在任何當前批處理語句調用的存儲過程和函數中),表變量在批處理結束後自動被清除。

 

2) 參考6中在"Recompilations Due to Certain Temporary Table Operations" 環節討論了臨時表在會致使存儲過程強制被重複編譯的各類緣由,但這些緣由並不適用於表變量。表變量和臨時表比起來會產生更少的存儲過程重編譯。

 

3) 針對表變量的事務僅僅在更新數據時生效,因此鎖和日誌產生的數量會更少。

 

4) 因爲表變量的做用域如此之小,並且不屬於數據庫的持久部分,因此事務回滾不會影響表變量。

 

    表變量能夠在其做用域內像正常的表同樣使用。更確切的說,表變量能夠被當成正常的表或者表表達式同樣在select,delete,update,insert語句中使用。可是表變量不能在相似「SELECT select_list INTO table_variable」 這樣的語句中使用。而在SQL Server 2000中,表變量也不能被用於「INSERT INTO table_variable EXEC stored_procedure」這樣的語句中。

 

    表變量不能作以下事情:

    1.雖然表變量是一個變量,可是其不能賦值給另外一個變量。

    2.check約束,默認值,和計算列不能引用自定義函數。

    3.不能爲約束命名。

    4.不能Truncate表變量

    5.不能向標識列中插入顯式值(也就是說表變量不支持SET IDENTITY_INSERT ON)

 

臨時表

    在深刻臨時表以前,咱們首先須要討論一下會話(Session),一個會話僅僅是一個客戶端到數據引擎的鏈接。在SQL Server Management Studio(SSMS)中,每個查詢窗口都會和數據庫引擎創建鏈接。一個應用程序能夠和數據庫創建一個或多個鏈接,除此以外,應用程序還可能創建鏈接後一直不釋放直到應用程序結束,也可能使用完釋放鏈接須要時創建鏈接。

    那麼,什麼是臨時表?在BOL (CREATE TABLE)中,咱們能夠知道臨時表和以Create table語句建立的表有着相同的物理構成,但臨時表與正常的表不一樣之處有:

 

1) 臨時表的名字不能超過116個字符,這是因爲數據庫引擎爲了辨別不一樣會話創建不一樣的臨時表,因此會自動在臨時表的名字後附加一串

 

2) 局部臨時表(以「#」開頭命名的)做用域僅僅在當前的鏈接內,從在存儲過程當中創建局部臨時表的角度來看,局部臨時表會在下列狀況被Drop:

    a.顯式調用DROP Table語句

    b.當局部臨時表在存儲過程內被建立時,存儲過程結束也就意味着局部臨時表被DROP

    c.當前會話結束,在會話內建立的全部局部臨時表都會被Drop

 

3) 全局臨時表(以「##」開頭命名的)在全部的會話內可見,因此在建立全局臨時表以前首先檢查其是否存在,不然若是已經存在,你將會獲得重複建立對象的錯誤.

    a.全局臨時表會在建立其的會話結束後被DROP,其它會話將不能對全局臨時表進行引用。

    b.引用是在語句級別進行,好比說下面例子:

        i.創建新的查詢窗口,運行以下語句:

     create table ##temp (RowID int)

        ii.再次開啓一個新的查詢建立,使用以下語句每5秒中對全局臨時表進行引用

     while 1=1 begin select * from ##temp waitfor delay '00:00:05' end

       iii.回到第一個窗口,關閉窗口

       iv.在下一個循環引用全局臨時表時,將產生錯誤

 

4) 不能對臨時表進行分區。

 

5) 不能對臨時表加外鍵約束 

6) 臨時表內列的數據類型不能定義成沒有在TempDb中沒有定義自定義數據類型(自定義數據類型是數據庫級別的對象,而臨時表屬於TempDb),因爲TempDb在每次SQL Server重啓後會被自動建立,因此你必須使用startup stored procedure來爲TempDb建立自定義數據類型。你也能夠經過修改Model數據庫來達到這一目標。

 

7) XML列不能定義成XML集合的形式,除非這個集合已經在TempDb中定義

 

    臨時表既能夠經過Create Table語句建立,也能夠經過」SELECT <select_list> INTO #table」語句建立。你還能夠針對臨時表使用」INSERT INTO #table EXEC stored_procedure」這樣的語句。

    臨時表能夠擁有命名的約束和索引。可是,當兩個用戶在同一時間調用同一存儲過程時,將會產生」There is already an object named ‘<objectname>’ in the database」這樣的錯誤。因此最好的作法是不用爲創建的對象進行命名,而使用系統分配的在TempDb中惟一的。6

    參考6談論了不少因爲臨時表而致使的存儲過程重編譯的緣由以及避免的方法。

 

誤區

    誤區1.表變量僅僅在內存中。

    誤區2.臨時表僅僅存儲在物理介質中

    這兩種觀點都是明顯的誤區,在參考1的Q4節。表變量都是在TempDb數據庫中建立,由於表變量存儲的數據有可能超過物理內存。除此以外,咱們發現只要內存足夠,表變量和臨時表都會在內存中建立和處理。它們也一樣能夠在任什麼時候間被存入磁盤。

    如何證實這點?請看下面代碼(在SQL Server 2000到2008中都有效)

-- make a list of all of the user tables currently active in the
 -- TempDB database
 if object_id('tempdb..#tempTables') is not null drop table #tempTables
 select name into #tempTables from tempdb..sysobjects where type ='U'
 -- prove that even this new temporary table is in the list.
 -- Note the suffix at the end of it to uniquely identify the table across sessions.
 select * from #tempTables where name like '#tempTables%'
 GO
 -- create a table variable
 declare @MyTableVariable table (RowID int)
 -- show all of the new user tables in the TempDB database.
 select name from tempdb..sysobjects
  where type ='U' and name not in (select name from #tempTables)

 

    還有一些「證實」臨時表僅僅存在於內存中謬誤,下面我來指出其中一個:

    注意表變量的名字是系統分配的,表變量的第一個字符」@」並非一個字母,因此它並非一個有效的變量名。系統會在TempDb中爲表變量建立一個系統分配的名稱,因此任何在sysobjects或sys.tables查找表變量的方法都會失敗。

    正確的方法應該是我前面例子中的方法,我看到不少人使用以下查詢查表變量:

 select * from sysobjects where name like'#tempTables%'
 
            
 
            

    上述代碼看上去貌似很好用,但會產生多用戶的問題。你創建兩個鏈接,在第一個鏈接中建立臨時表,在第二個窗口中運行上面的語句能看到第一個鏈接建立的臨時表,若是你在第二個鏈接中嘗試操做這個臨時表,那麼可能會產生錯誤,由於這個臨時表不屬於你的會話。

 

誤區3.表變量不能擁有索引。

    這個誤區也一樣錯誤。雖然一旦你建立一個表變量以後,就不能對其進行DDL語句了,這包括Create Index語句。然而你能夠在表變量定義的時候爲其建立索引)好比以下語句.

declare @MyTableVariable table (RowID intPRIMARY KEY CLUSTERED) 

    這個語句將會建立一個擁有彙集索引的表變量。因爲主鍵有了對應的彙集索引,因此一個系統命名的索引將會被建立在RowID列上。

    下面的例子演示你能夠在一個表變量的列上建立惟一約束以及如何創建符合索引。

 declare @temp TABLE ( RowID int NOT NULL, ColA int NOT NULL, ColB char(1)UNIQUE, PRIMARY KEY CLUSTERED(RowID, ColA))

1) SQL 並不能爲表變量創建統計信息,就像其能爲臨時表創建統計信息同樣。這意味着對於表變量,執行引擎認爲其只有1行,這也意味着針對表變量的執行計劃並非最優。雖然估計的執行計劃對於表變量和臨時表都爲1,可是實際的執行計劃對於臨時表會根據每次存儲過程的重編譯而改變(看參考1,Q2部分).若是臨時表不存在,在生成執行計劃的時候會產生錯誤。

 

2) 前面提到,必定創建表變量後就沒法對其進行DDL語句操做。所以若是須要爲表創建索引或者加一列,你須要臨時表。

 

3) 表變量不能使用select …into語句,而臨時表能夠

 

4) 在SQL Server 2008中,你能夠將表變量做爲參數傳入存儲過程。可是臨時表不行。在SQL Server 2000和2005中表變量也不行。

 

5) 做用域:表變量僅僅在當前的批處理中有效,而且對任何在其中嵌套的存儲過程等不可見。局部臨時表只在當前會話中有效,這也包括嵌套的存儲過程。但對父存儲過程不可見。全局臨時表能夠在任何會話中可見,可是會隨着建立其的會話終止而DROP,其它會話這時就不能再引用全局臨時表。

 

6) 排序規則:表變量使用當前數據庫的排序規則,臨時表使用TempDb的排序規則。若是它們不兼容,你還須要在查詢或者表定義中進行指定(參考7.Table Variables and Temporary Tables)

 

7) 你若是但願在動態SQL中使用表變量,你必須在動態SQL中定義表變量。而臨時表能夠提早定義,在動態SQL中進行引用。

 

說了這麼多,那麼,我該如何選擇呢?

    微軟推薦使用表變量(看參考4),若是表中的行數很是小,則使用表變量。不少」網絡專家」會告訴你100是一個分界線,由於這是統計信息建立查詢計劃效率高低的開始。可是我仍是但願告訴你針對你的特定需求對臨時表和表變量進行測試。不少人在自定義函數中使用表變量,若是你須要在表變量中使用主鍵和惟一索引,你會發現包含數千行的表變量也依然性能卓越。但若是你須要將表變量和其它表進行join,你會發現因爲不精準的執行計劃,性能每每會很是差。

    爲了證實這點,請看本文的附件。附件中代碼建立了表變量和臨時表.並裝入了AdventureWorks數據庫的Sales.SalesOrderDetail表。爲了獲得足夠的測試數據,我將這個表中的數據插入了10遍。而後以ModifiedDate 列做爲條件將臨時表和表變量與原始的Sales.SalesOrderDetail表進行了Join操做,從統計信息來看IO差異顯著。從時間來看錶變量作join花了50多秒,而臨時表僅僅花了8秒。

    若是你須要在表創建後對錶進行DLL操做,那麼選擇臨時表吧。

    臨時表和表變量有不少相似的地方。因此有時候並無具體的細則規定如何選擇哪個。對任何特定的狀況,你都須要考慮其各自優缺點並作一些性能測試。下面的表格會讓你比較其優略有了更詳細的參考。

 

總結

特性 表變量 臨時表
做用域 當前批處理 當前會話,嵌套存儲過程,全局:全部會話
使用場景 自定義函數,存儲過程,批處理 自定義函數,存儲過程,批處理
建立方式 DECLARE statement only.只能經過DECLEARE語句建立

CREATE TABLE 語句

SELECT INTO 語句.

表名長度 最多128字節 最多116字節
列類型

可使用自定義數據類型

可使用XML集合

自定義數據類型和XML集合必須在TempDb內定義
Collation 字符串排序規則繼承自當前數據庫 字符串排序規則繼承自TempDb數據庫
索引 索引必須在表定義時創建 索引能夠在表建立後創建
約束 PRIMARY KEY, UNIQUE, NULL, CHECK約束可使用,但必須在表創建時聲明 PRIMARY KEY, UNIQUE, NULL, CHECK. 約束可使用,能夠在任什麼時候後添加,但不能有外鍵約束
表創建後使用DDL (索引,列) 不容許 容許.
數據插入方式 INSERT 語句 (SQL 2000: 不能使用INSERT/EXEC).

INSERT 語句, 包括 INSERT/EXEC.

SELECT INTO 語句.

Insert explicit values into identity columns (SET IDENTITY_INSERT). 不支持SET IDENTITY_INSERT語句 支持SET IDENTITY_INSERT語句
Truncate table 不容許 容許
析構方式 批處理結束後自動析構 顯式調用 DROP TABLE 語句. 
當前會話結束自動析構 (全局臨時表: 還包括當其它會話語句不在引用表.)
事務 只會在更新表的時候有事務,持續時間比臨時表短 正常的事務長度,比表變量長
存儲過程重編譯 會致使重編譯
回滾 不會被回滾影響 會被回滾影響
統計數據 不建立統計數據,因此全部的估計行數都爲1,因此生成執行計劃會不精準 建立統計數據,經過實際的行數生成執行計劃。
做爲參數傳入存儲過程 僅僅在SQL Server2008, 而且必須預約義 user-defined table type. 不容許
顯式命名對象 (索引, 約束). 不容許 容許,可是要注意多用戶的問題
動態SQL 必須在動態SQL中定義表變量 能夠在調用動態SQL以前定義臨時表

 

參考:

1) INF: Frequently Asked Questions - SQL Server 2000 - Table Variables

2) T-SQL BOL (SQL 2000), table data type

3) T-SQL BOL (SQL 2008), Declare @local_variable

4) T-SQL BOL (SQL 2008), CREATE TABLE

5) Table-Valued Parameters (Database Engine)

6) Troubleshooting stored procedure recompilation

7) Local Temporary Tables and Table Variables

8) Startup stored procedure

9) Data Definition Language (DDL)

其它值得閱讀的文章:

1) Things You Didn’t Know About Temp Tables and Table Variables

 

 

 

-----------------------------------------------------------------------

原文連接:http://www.sqlservercentral.com/articles/Temporary+Tables/66720/

Translated by:CareySon

 

 

對於表列數據類型選擇的一點思考

 

簡介

    SQL Server每一個表中各列的數據類型的選擇一般顯得很簡單,可是對於具體數據類型的選擇的不一樣對性能的影響仍是略有差異。本篇文章對SQL Server表列數據類型的選擇進行一些探索。

 

一些數據存儲的基礎知識

    在SQL Server中,數據的存儲以頁爲單位。八個頁爲一個區。一頁爲8K,一個區爲64K,這個意味着1M的空間能夠容納16個區。如圖1所示:

    1

    圖1.SQL Server中的頁和區

 

    如圖1(PS:發現用windows自帶的畫圖程序畫博客中的圖片也不錯微笑)能夠看出,SQL Server中的分配單元分爲三種,分別爲存儲行內數據的In_Row_Data,存儲Lob對象的LOB_Data,存儲溢出數據的Row_Overflow_data。下面咱們經過一個更具體的例子來理解這三種分配單元。

    我創建如圖2所示的表。

    2

    圖2.測試表

 

    圖2的測試表不難看出,經過插入數據使得每一行的長度會超過每頁所能容納的最大長度8060字節。使得不只產生了行溢出(Row_Overflow_Data),還須要存儲LOB的頁.測試的插入語句和經過DBCC IND看到的分配狀況如圖3所示。

    3

    圖3.超過8060字節的行所分配的頁

 

    除去IAM頁,這1行數據所須要三個頁來存儲。首先是LOB頁,這類是用於存儲存在數據庫的二進制文件所設計,當這個類型的列出現時,在原有的列會存儲一個24字節的指針,而將具體的二進制數據存在LOB頁中,除去Text以外,VarBinary(max)也是存在LOB頁中的。而後是溢出行,在SQL Server 2000中,一行超過8060字節是不被容許的,在SQL Server 2005以後的版本對這個特性進行了改進,使用Varchar,nvarchar等數據類型時,當行的大小不超過8060字節時,所有存在行內In-row data,當varchar中存儲的數據過多使得整行超過8060字節時,會將額外的部分存於Row-overflow data頁中,若是update這列使得行大小減小到小於8060字節,則這行又會所有回到in-row data頁。

 

數據類型的選擇

    在瞭解了一些基礎知識以後。咱們知道SQL Server讀取數據是以頁爲單位,更少的頁不只僅意味着更少的IO,還有更少的內存和CPU資源消耗。因此對於數據選擇的主旨是:

    儘可能使得每行的大小更小

    這個聽起來很是簡單,但實際上還須要對SQL Server的數據類型有更多的瞭解。

    好比存儲INT類型的數據,按照業務規則,能用INT就不用BIGINT,能用SMALLINT就不用INT,能用TINYINT就不用SMALLINT。

    因此爲了使每行的數據更小,則使用佔字節最小的數據類型。

 

   1.好比不要使用DateTime類型,而根據業務使用更精確的類型,以下表:

類型 所佔字節
Date(僅日期) 3
Time(僅時間) 5
DateTime2(時間和日期) 8
DateTimeOffSet(外加時區) 10

 

    2.使用VarChar(Max),Nvarchar(Max),varbinary(Max)來代替text,ntext和image類型

    根據前面的基礎知識能夠知道,對於text,ntext和image類型來講,每一列只要不爲null,即便佔用很小的數據,也須要額外分配一個LOB頁,這無疑佔用了更多的頁。而對於Varchar(Max)等數據類型來講,當數據量很小的時候,存在In-row-data中就能知足要求,而不用額外的LOB頁,只有當數據溢出時,纔會額外分配LOB頁,除此以外,Varchar(Max)等類型支持字符串操做函數好比:

  • COL_LENGTH
  • CHARINDEX
  • PATINDEX
  • LEN
  • DATALENGTH
  • SUBSTRING

 

    3.對於僅僅存儲數字的列,使用數字類型而不是Varchar等。

     由於數字類型佔用更小的存儲空間。好比存儲123456789使用INT類型只須要4個字節,而使用Varchar就須要9個字節(這還不包括Varchar還須要佔用4個字節記錄長度)。

 

    4.若是沒有必要,不要使用Nvarchar,Nchar等以「字」爲單位存儲的數據類型。這類數據類型相比varchar或是char須要更多的存儲空間。

 

    5.關於Char和VarChar的選擇

     這類比較其實有一些了。若是懶得記憶,大多數狀況下使用Varchar都是正確的選擇。咱們知道Varchar所佔用的存儲空間由其存儲的內容決定,而Char所佔用的存儲空間由定義其的長度決定。所以Char的長度不管存儲多少數據,都會佔用其定義的空間。因此若是列存儲着像郵政編碼這樣的固定長度的數據,選擇Char吧,不然選擇Varchar會比較好。除此以外,Varchar相比Char要多佔用幾個字節存儲其長度,下面咱們來作個簡單的實驗。

    首先咱們創建表,這個表中只有兩個列,一個INT類型的列,另外一個類型定義爲Char(5),向其中插入兩條測試數據,而後經過DBCC PAGE來查看其頁內結構,如圖4所示。

    4 
    圖4.使用char(5)類型,每行所佔的空間爲16字節

 

    下面咱們再來看改成Varchar(5),此時的頁信息,如圖5所示。

    5

    圖5.Varchar(5),每行所佔用的空間爲20字節

 

    所以能夠看出,Varchar須要額外4個字節來記錄其內容長度。所以,當實際列存儲的內容長度小於5字節時,使用char而不是varchar會更節省空間。

 

關於Null的使用

    關於Null的使用也是略有爭議。有些人建議不要容許Null,所有設置成Not Null+Default。這樣作是因爲SQL Server比較時就不會使用三值邏輯(TRUE,FALSE,UNKNOWN),而使用二值邏輯(True,False),而且查詢的時候也再也不須要IsNull函數來替換Null值。

    但這也引出了一些問題,好比聚合函數的時候,Null值是不參與運算的,而使用Not Null+Default這個值就須要作排除處理。

    所以Null的使用還須要按照具體的業務來看。

 

考慮使用稀疏列(Sparse)

    稀疏列是對 Null 值採用優化的存儲方式的普通列。 稀疏列減小了 Null 值的空間需求,但代價是檢索非 Null 值的開銷增長。 當至少可以節省 20% 到 40% 的空間時,才應考慮使用稀疏列。

    稀疏列在SSMS中的設置如圖6所示。

    6

    圖6.稀疏列

 

    更具體的稀疏列如何能節省空間,請參看MSDN

 

對於主鍵的選擇

     對於主鍵的選擇是表設計的重中之重,由於主鍵不只關係到業務模型,更關係到對錶數據操做的的效率(由於主鍵會處於B樹的非葉子節點中,對樹的高度的影響最多)。關於主鍵的選擇,我以前已經有一篇文章關於這點:從性能的角度談SQL Server彙集索引鍵的選擇,這裏就再也不細說了。

 

總結

    本篇文章對於設計表時,數據列的選擇進行了一些探尋。好的表設計不只僅是能知足業務需求,還可以知足對性能的優化。

 

 

SQL Server複製入門(一)----複製簡介

 

簡介

    SQL Server中的複製(Replication)是SQL Server高可用性的核心功能之一,在我看來,複製指的並不只僅是一項技術,而是一些列技術的集合,包括從存儲轉發數據到同步數據到維護數據一致性。使用複製功能不只僅須要你對業務的熟悉,還須要對複製功能的總體有一個全面的瞭解,本系列文章旨在對SQL Server中的複製進行一個簡單全面的探討。(PS:在個人上篇文章中我發現某些文章的圖片使用mspaint手繪更有感受,但被不少人吐槽,所以在不考慮我的羞恥感的前提下,本系列文章中的一些圖片繼續使用mspaint微笑)。

 

複製是什麼

    複製,英文是Replication,這個詞源自於拉丁文replicare,原意是重複。SQL Server中的複製也是這個意思,複製的核心功能是存儲轉發,意味着在一個在一個位置增刪改了數據之後,重複這個動做到其餘的數據源,概念如圖1所示。

    1

    圖1.複製的基本概念

 

    固然,上面的這個模型是複製最簡單的模型,實際中的模型可能會複雜不少,可是大多數使用複製的緣由能夠分爲以下幾類:

    1.負載均衡----經過將數據複製到其它數據庫服務器來減小當前服務器的負載,好比說最典型的應用就是分發數據來分離OLTP和OLAP環境。

    2.分區----將常用的數據和歷史數據隔離,將歷史數據複製到其它數據庫中

    3.受權----將一部分數據提供給須要使用數據的人,以供其使用

    4.數據合併-每一個區域都有其各自的數據,將其數據進行合併。好比一個大公司,每一個地區都有其各自的銷售數據,總部須要彙總這些數據。

    5.故障轉移----複製全部數據,以便故障時進行轉移。

 

    雖然須要使用複製的緣由多種多樣,可是在使用以前你首先要了解複製技術所需的組成元素。

 

複製的組成部分

     複製的概念很像發行雜誌的模型,從發行商那裏出版後,須要經過報刊亭等地方分發到訂閱雜誌的人手裏。對於SQL Server複製來講,這個概念也是如此。對於SQL Server複製來講,發行商,報刊亭,訂閱者分別對應的是發佈服務器,分發服務器,訂閱服務器。概念如圖2所示。

    2

    圖2.發佈分發訂閱的基本概念

 

發佈服務器

   圖2中的發佈服務器包含了須要被髮布的數據庫。也就是須要向其它數據源分發內容的源數據庫。固然,被髮布的數據首先須要被容許發佈。關於這裏的詳細設置會在文章後面提到。

分發服務器

    圖2中的分發服務器包含了分發數據庫,分發數據庫的做用是存儲轉發發佈服務器發過來的數據。一個分發服務器支持多個發佈服務器,就像一個報刊亭能夠出售多個出版社所出的雜誌同樣。同理,分發服務器也能夠和發佈服務器是同一個實例,這就像出版商不經過報刊亭,本身直接販賣雜誌同樣。

訂閱服務器

    圖2中的訂閱服務器包含了發佈服務器所發佈的數據的副本。這個副本能夠是一個數據庫,或者一個表,甚至是一個表的子集。根據不一樣的設置,有些發佈服務器發佈的更新到訂閱服務器就是隻讀的(好比說用於出報表的OLAP環境),或者是訂閱服務器也能夠進行更新來將這些改變提交到發佈服務器。

 

發佈和文章

    發佈指的是能夠發佈的文章的集合,這些文章包括表,存儲過程,視圖和用戶自定義函數,如圖3所示。

    3

    圖3.能夠發佈的內容

 

    當咱們發佈表時,還能夠根據限定條件只發布表的子集。

 

訂閱

    訂閱是相對發佈的一個概念,訂閱定義了訂閱服務器從哪一個分發服務器接收發布。有兩類訂閱方式,推送訂閱(Push)和請求訂閱(Pull),根據名字就能夠望文生義的知道,在推送訂閱的狀況下,當發佈服務器產生更新時,分發服務器直接更新訂閱的內容,而請求訂閱須要訂閱服務器按期查看分發服務器是否有可用更新,若是存在可用更新,則訂閱服務器更新數據。

 

複製類型

    SQL Server將複製方式分爲三大類,每個發佈只能有一種複製類型,分別爲:快照複製,事務複製和合並複製。

 

快照複製

   快照複製將發佈的全部表作成一個鏡像,而後一次性複製到訂閱服務器。中間的更新不會像其它複製類型那樣自動傳送到訂閱服務器。由這個概念不難看出,快照複製的特色會是:

   1.佔用網絡寬帶,由於一次性傳輸整個鏡像,因此快照複製的內容不該該太大。

   2.適合那些更新不頻繁,但每次更新都比較大的數據。好比企業員工信息表,每半年更新一次這類的業務場景。

   3.適合訂閱服務器是OLAP只讀的環境。

  

   來自MSDN的配圖能很好的闡述快照複製,如圖4所示。

    snap

    圖4.快照複製

 

事務複製

    事務複製就像其名字同樣,複製事務。在第一次設置好事務複製後,發佈的表、存儲過程等將會被鏡像,以後每次對於發佈服務器所作的改動都會以日誌的方式傳送到訂閱服務器。使得發佈服務器和訂閱服務器幾乎能夠保持同步。所以,能夠看出事務複製的特色是:

    1.發佈服務器和訂閱服務器內容基本能夠同步

    2.發佈服務器,分發服務器,訂閱服務器之間的網絡鏈接要保持暢通。

    3.訂閱服務器也能夠設置成請求訂閱,使得訂閱服務器也能夠不用一直和分發服務器保持鏈接。

    4.適用於要求實時性的環境。

 

    來自MSDN的配圖能很好的闡述事務複製,如圖5所示

    grid.ai

    圖5.事務複製

 

合併複製

    合併複製即容許發佈服務器更新數據庫,也容許訂閱服務器更新數據。按期將這些更新進行合併,使得發佈的數據在全部的節點上保持一致。所以,有可能發佈服務器和訂閱服務器更新了一樣的數據,當衝突產生時,並非徹底按照發布服務器優先來處理衝突,而是根據設置進行處理,這些會在後續文章中講到。

  

    來自MSDN的配圖能很好的闡述合併複製,如圖6所示。

    merge

     圖6.合併複製

 

創建一個簡單的事務複製

    下面我進行一個簡單的事務複製。首先,在本地安裝兩個SQL Server實例,我本機安裝的兩個實例分別爲SQL Server 2008R2和SQL Server 2012,其中,SQL Server 2008R2做爲發佈和分發服務器,SQL Server 2012做爲訂閱服務器,如圖7所示。

    7

    圖7.複製的兩個實例

 

    首先在SQL Server 2008R2上配置發佈服務器和分發服務器,選擇配置分發,如圖8所示。

    8

    圖8.配置分發

 

    將發佈服務器和分發服務器選擇爲同1臺,如圖9所示。

    9

    圖9.設置發佈服務器和分發服務器爲同一臺服務器

 

    設置快照文件夾,由上面MSDN的圖可知,快照代理是須要在分發服務器上暫存快照的,設置這個目錄,如圖10所示。

    10

    圖10.設置快照文件夾

 

    這裏值得注意的是,須要給這個目錄對於Everyone設置讀取權限,如圖11所示。

    11

    圖11.設置讀取權限

 

    下一步配置分發嚮導就按照默認值來,如圖12所示。

    12

    圖12.配置分發嚮導

 

    剩下的步驟都保持默認值,最後成功在SQL Server 2008R2實例上配置發佈服務器和分發服務器,如圖13所示。

    13

    圖13.成功配置發佈和分發服務器

 

    下面就要創建一個發佈了,選擇新建發佈,如圖14所示。

    14

    圖14.新建發佈

 

   一路next,在選擇發佈類型時選擇事務發佈,如圖15所示。

    15

    圖15.選擇事務發佈

 

   發佈用於測試的一個表,只有兩個列,一個爲自增的int型主鍵id,另外一個爲隨便設置的列,如圖16所示。

   16

  圖16.設置發佈的表(文章)

 

   下一個頁面不過濾文章,直接保持默認值下一步。在下一個窗口中選擇當即建立快照並初始化..如圖17所示。

    17

    圖17.當即創造快照並初始化

 

   安全設置保持和SQL Server Agent同樣的帳戶,如圖18所示。

   18

    圖18.快照代理和日誌讀取代理設置和SQL Server Agent同一個帳戶

 

  剩下的步驟一路下一步,設置好發佈名稱後,成功建立發佈,如圖19所示。

  19

   圖19.成功建立發佈

 

   下面咱們來在SQL Server 2012的實例上建立訂閱,選擇新建訂閱,如圖20所示。

    20

    圖20.新建訂閱

 

    在歡迎界面選擇下一步後,選擇剛剛建立的發佈,如圖21所示。

    21

    圖21.選擇發佈服務器

 

    下一步選擇推送訂閱,以便發佈服務器所作的改動能自動更改到訂閱服務器,如圖22所示。

    23

    圖23.選擇推送訂閱

 

   選擇保持鏈接,下一步保持默認值,而後在分發代理安全性下選擇模擬進程帳戶。如圖24所示。

    24

    圖24.選擇模擬進程帳戶

 

    保持默認值,一路下一步直到訂閱建立完成,如圖25所示。

    25

    圖25.建立訂閱成功

 

    如今咱們進行測試,向表中插入100條數據,監視狀態,發現100個事務已經成功傳到了訂閱服務器,如圖26所示。

    26

    圖26.插入的100條數據已經成功傳送到訂閱服務器

 

    如今咱們再來看訂閱服務器(SQL Server 2012),在發佈服務器插入的100條數據已經成功存在於訂閱服務器,如圖27所示。

   27

    圖27.100條數據已經成功發佈到了訂閱服務器

 

 

總結

    本文對SQL Server的複製進行了大體的講解,並實現了一個簡單的複製。複製的概念須要對SQL Server的各個方面都要有所涉獵,本系列文章的下一篇將會將複製應用的一些模式。

 

 

操做系統中的進程與線程

 

簡介

    在傳統的操做系統中,進程擁有獨立的內存地址空間和一個用於控制的線程。可是,如今的狀況更多的狀況下要求在同一地址空間下擁有多個線程併發執行。所以線程被引入操做系統。

 

 

爲何須要線程?

    若是非要說是爲何須要線程,還不如說爲何須要進程中還有其它進程。這些進程中包含的其它迷你進程就是線程。

    線程之因此說是迷你進程,是由於線程和進程有不少類似之處,好比線程和進程的狀態都有運行,就緒,阻塞狀態。這幾種狀態理解起來很是簡單,當進程所需的資源沒有到位時會是阻塞狀態,當進程所需的資源到位時但CPU沒有到位時是就緒狀態,當進程既有所需的資源,又有CPU時,就爲運行狀態。

    下面咱們來看一個具體的例子:

     就拿我寫博客的LiveWriter來講,LiveWriter須要監聽我打字輸入的狀態,還須要每隔5分鐘對草稿進行自動保存。假設若是這個進程只有一個線程的話,那麼當對草稿進行保存時,由於此時須要訪問硬盤,而訪問硬盤的時間線程是阻塞狀態的,這時個人任何輸入都會沒有響應,這種用戶體驗是沒法接受的,或許咱們能夠經過鍵盤或者鼠標的輸入去中斷保存草稿的過程,但這種方案也並不討好。而使用多線程,每一個線程僅僅須要處理本身那一部分應該完成的任務,而不用去關心和其它線程的衝突。所以簡化了編程模型。如圖1所示。

    1

    圖1.兩條線程知足各自的功能

   

    更具體的說,線程的好處以下:

    1.在不少程序中,須要多個線程互相同步或互斥的並行完成工做,而將這些工做分解到不一樣的線程中去無疑簡化了編程模型。

    2.由於線程相比進程來講,更加的輕量,因此線程的建立和銷燬的代價變得更小。

    3.線程提升了性能,雖然線程宏觀上是並行的,但微觀上倒是串行。從CPU角度線程並沒有法提高性能,但若是某些線程涉及到等待資源(好比IO,等待輸入)時,多線程容許進程中的其它線程繼續執行而不是整個進程被阻塞,所以提升了CPU的利用率,從這個角度會提高性能。

    4.在多CPU或多核的狀況下,使用線程不只僅在宏觀上並行,在微觀上也是並行的。

 

    這裏值得注意的是,上面的兩個線程若是改爲兩個進程,那麼達不到所要的效果,由於進程有本身獨立的內存地址空間,而線程共享進程的內存地址空間

 

經典線程模型

    另外一個看進程和線程的角度是進程模型基於兩類不一樣的概念:資源的組織和執行。在過去沒有線程的操做系統中,資源的組織和執行都是由進程完成的。但區分這二者不少時候須要加以區分,這也是爲何須要引入線程。

    進程是用於組織資源的單位,進程將相關的資源組織在一塊兒,這些資源包括:內存地址空間,程序,數據等,將這些以進程的形式組織起來可使得操做系統管理這些資源更爲容易。

    而線程,是每個進程中執行的一個條線。線程雖然共享進程中的大多數資源,但線程也須要本身的一些資源,好比:用於標識下一條執行指令的程序計數器,一些容納局部變量的寄存器,以及用於表示執行的歷史的棧。

    總而言之:進程是組織資源的最小單位,而線程是安排CPU執行的最小單位

    其實在一個進程中多個線程並行和在操做系統中多個進程並行很是相似,只是線程共享的是地址空間,而進程共享的是物理內存,打印機,鍵盤等資源……

    每個進程和線程所獨自佔有的資源如表1所示。

      
進程佔有的資源 線程佔有的資源
地址空間 
全局變量 
打開的文件 
子進程 
信號量 
帳戶信息
棧 
寄存器 
狀態 
程序計數器

表1.進程和線程所獨佔的資源

 

    其中,線程能夠共享進程獨佔的資源。

 

    咱們經常使用的術語「多線程」通常指的是在同一個進程中多個線程的併發執行。如圖2所示。

    2

    圖2.沒有多線程的系統一個進程只能由一個線程

 

    在多線程的進程中,每一個線程輪流使用CPU,所以實際上線程並非並行的,但從宏觀上看,是並行的。

    在多線程模型中,每個進程初始建立時只有一個線程。這個線程能夠經過調用系統的庫函數去建立其它線程。線程建立的線程並必需要爲其指定地址,由於新的線程自動在建立它的地址空間內工做。雖然一個線程能夠建立另外一個線程,但一般來說,線程之間是並列的,並不存在層級關係。

    當一個進程完成其工做後,能夠經過調用系統庫函數進行銷燬。

 

操做系統實現線程的幾種模式

     在操做系統中,線程能夠實如今用戶模式下,也能夠實如今內核模式下,也能夠二者結合實現。

 

線程實如今用戶空間下

    當線程在用戶空間下實現時,操做系統對線程的存在一無所知,操做系統只能看到進程,而不能看到線程。全部的線程都是在用戶空間實現。在操做系統看來,每個進程只有一個線程。過去的操做系統大部分是這種實現方式,這種方式的好處之一就是即便操做系統不支持線程,也能夠經過庫函數來支持線程。

    在這種模式下,每個進程中都維護着一個線程表來追蹤本進程中的線程,這個表中包含表1中每一個線程獨佔的資源,好比棧,寄存器,狀態等,如圖3所示。

    3

    圖3.在用戶空間中實現線程

 

    這種模式當一個線程完成了其工做或等待須要被阻塞時,其調用系統過程阻塞自身,而後將CPU交由其它線程。

    這種的模式的好處,首先,是在用戶空間下進行進程切換的速度要遠快於在操做系統內核中實現。其次,在用戶空間下實現線程使得程序員能夠實現本身的線程調度算法。好比進程能夠實現垃圾回收器來回收線程。還有,當線程數量過多時,因爲在用戶空間維護線程表,不會佔用大量的操做系統空間。

    有好處就有壞處,這種模式最致命的缺點也是因爲操做系統不知道線程的存在,所以當一個進程中的某一個線程進行系統調用時,好比缺頁中斷而致使線程阻塞,此時操做系統會阻塞整個進程,即便這個進程中其它線程還在工做。還有一個問題是假如進程中一個線程長時間不釋放CPU,由於用戶空間並無時鐘中斷機制,會致使此進程中的其它線程得不到CPU而持續等待。

 

線程實如今操做系統內核中

     在這種模式下,操做系統知道線程的存在。此時線程表存在操做系統內核中,如圖4所示。

     4

     圖4.線程在操做系統內核中實現

 

    在這種模式下,全部可能阻塞線程的調用都以系統調用(System Call)的方式實現,相比在用戶空間下實現線程形成阻塞的運行時調用(System runtime call)成本會高出不少。當一個線程阻塞時,操做系統能夠選擇將CPU交給同一進程中的其它線程,或是其它進程中的線程,而在用戶空間下實現線程時,調度只能在本進程中執行,直到操做系統剝奪了當前進程的CPU。

    由於在內核模式下實現進程的成本更高,一個比較好的作法是另線程回收利用,當一個線程須要被銷燬時,僅僅是修改標記位,而不是直接銷燬其內容,當一個新的線程須要被建立時,也一樣修改被「銷燬」的線程其標記位便可。

    這種模式下一樣仍是有一些弊端,好比接收系統信號的單位是進程,而不是線程,那麼由進程中的哪個線程接收系統信號呢?若是使用了表來記錄,那麼多個線程註冊則經過哪個線程處理系統信號?

 

混合模式

    還有一種實現方式是將上面兩種模式進行混合,用戶空間中進程管理本身的線程,操做系統內核中有一部份內核級別的線程,如圖5所示。

    5

      圖5.混合模式

 

     在這種模式下,操做系統只能看到內核線程。用戶空間線程基於操做系統線程運行。所以,程序員能夠決定使用多少用戶空間線程以及操做系統線程,這無疑具備更大的靈活性。而用戶空間線程的調度和前面所說的在用戶空間下執行實現線程是同樣的,一樣能夠自定義實現。

相關文章
相關標籤/搜索