本人新書上市,請多多關照:《SQL Server On Linux運維實戰 2017版從入門到精通》
本篇開始專門對性能進行一系列講解,這一系列不限於Linux平臺,更多的是針對SQL Server本身。
SQL Server性能新特性
SQL Server發展至今,爲了不斷提升性能,引入了不少最新技術。它們主要以分區表/索引,In-Memory OLTP 和列存儲索引這三類核心技術爲主。雖然這些技術有各自的使用場景,但是都具有一個共同的目標:針對關鍵系統大幅度提高性能。這些功能不僅僅是提升性能,而且還是大幅度提升!
接下來會簡單介紹這三種技術,有機會的話再細說具體技術。由於工作需要,首先從分區表和分區索引入手。
分區表和分區索引
這個嚴格意義來說不能稱爲“新技術“,因爲從SQL Server 2005開始就有了。而且業界很多數據庫產品都有分區功能。分區的出現源自於大量關係數據的出現。當一個表存儲了億級(請更新你對幾百萬行數據就成爲“大表“的叫法),增刪改查的性能將明顯下降,其維護成本(比如備份/重建索引/更新統計信息等等)都會變得非常大。
因此,業界通常會對錶進行拆分操作。拆分通常有***垂直拆分***和***橫向拆分***, 垂直拆分大概就是把一個有很多列的表,按照業務邏輯,拆成多個行數相等但是列數更少的表。這種拆分***並不減少數據行數***,但是可以減少列數,可以減少很多開銷,特別是索引相關的開銷。橫向拆分也是根據業務,把數據按某些條件來拆分搬到別的相同結構的新表中,比如按照日期、地區等,把一個存有全國一年數據的表,拆成34個表(按照省級行政區域),也可以拆成12個表(按照月份)或者綜合這兩種方法,使其體積降到原有的1/n,大大減少單表體積,增大數據操作的性能。
其中垂直拆分通常會針對明細表或者DW類型的寬表,因爲這個方式更多是減少列數。但是對於常規的OLTP系統而言,其問題往往在於行數太多,這時候通常用橫向拆分,也就是把表的部分數據搬到其他表中。
在SQL Server 2000時代,只能通過把數據拆到“實體”表中,實現橫向拆分,但是這種方式下,表的個數可能會突增,而且需要很多額外維護操作,在編寫SQL語句時,也需要使用類似UNION/UNION ALL或者視圖等方式來合併起來,同時需要記住某些篩選條件需要訪問哪些具體的實體表,總而言之,用起來很不友好。
從SQL 2005開始,引入了分區技術,簡單來說,它就是由SQL Server幫你管理分區的功能,最大限度降低運維和使用成本的前提下面,減少最終直接操作的表的數據行數。不過需要提醒的是,分區實際上跟垂直和橫向拆分是不一樣的。
從業務出發,有一些數據天生就是可被“切片”的。比如前面提到的地區、時間,或者種類這些相對固化的特性。使用分區之前,先了解一下技術概念:
- 分區函數(partition function):用於通過使用一個範圍的值來定義分區的數量和分區的範圍(邊界) 。就是定義哪個列的哪裏到哪裏的值屬於某個分區。(定義範圍)
- 分區方案(partition scheme):定義分區函數使用那些文件組,通常一個分區會映射到一個或多個“用戶”文件組上,但是這個過程只能用TSQL命令實現。(定義物理存儲)
- 分區列(partition column):是具體分區的依據,分區函數配合這個列及其值來做分區,最常用的分區列就是日期了。
很多人聽說過,分區的優點源自於數據量的減少。但是從技術來說,應該是來自於叫“partition elimination”(分區消除)。
但是很多人又可能認爲,一旦出現了掃描操作,那麼就意味着沒有使用partition elimination,後面將會演示一下分區消除的內容。
分區演示
在本篇中,我們先快速演示一下簡單的分區創建:
我們使用WideWorldImporters庫做演示,因爲這個庫已經做了相應的分區,所以我們這裏就不實操創建過程,不過可以展示一下,我們可以在數據庫的【存儲】看到創建了什麼分區函數和分區方案:
然後查看紅框的兩個地方看一下它們具體的實現:
導出來的腳本如下:
USE [WideWorldImporters]
GO
/****** Object: PartitionFunction [PF_TransactionDate] Script Date: 2020/1/8 11:20:30 ******/
CREATE PARTITION FUNCTION [PF_TransactionDate](date) AS RANGE RIGHT
FOR VALUES (N'2014-01-01T00:00:00.000', N'2015-01-01T00:00:00.000',
N'2016-01-01T00:00:00.000', N'2017-01-01T00:00:00.000')
GO
USE [WideWorldImporters]
GO
/****** Object: PartitionScheme [PS_TransactionDate] Script Date: 2020/1/8 11:20:13 ******/
CREATE PARTITION SCHEME [PS_TransactionDate] AS PARTITION [PF_TransactionDate]
TO ([USERDATA], [USERDATA], [USERDATA], [USERDATA], [USERDATA], [USERDATA])
GO
下面來解釋一下:
- 分區函數定義了分區是基於一個date類型的列,同時指定了5個分區,每個分區是一個自然年。RANGE RIGHT意味着第五個分區是所有≥2017-01-01的值。
- 分區方案把分區函數映射到USERGROUP的文件組中,但是這個文件組包含了5個USERDATA的文件。這種方式可以分攤I/O性能和減緩單個磁盤的空間壓力。並且可以通過使用“文件組”備份來處理超大規模的數據庫備份工作。
現在回到表上面,我們導出表的腳本看看:
CREATE TABLE [Sales].[CustomerTransactions](
[CustomerTransactionID] [int] NOT NULL,
[CustomerID] [int] NOT NULL,
[TransactionTypeID] [int] NOT NULL,
[InvoiceID] [int] NULL,
[PaymentMethodID] [int] NULL,
[TransactionDate] [date] NOT NULL,
[AmountExcludingTax] [decimal](18, 2) NOT NULL,
[TaxAmount] [decimal](18, 2) NOT NULL,
[TransactionAmount] [decimal](18, 2) NOT NULL,
[OutstandingBalance] [decimal](18, 2) NOT NULL,
[FinalizationDate] [date] NULL,
[IsFinalized] AS (case when [FinalizationDate] IS NULL then
CONVERT([bit],(0)) else CONVERT([bit],(1)) end) PERSISTED,
[LastEditedBy] [int] NOT NULL,
[LastEditedWhen] [datetime2](7) NOT NULL,
CONSTRAINT [PK_Sales_CustomerTransactions] PRIMARY KEY NONCLUSTERED
(
[CustomerTransactionID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [USERDATA]
) ON [PS_TransactionDate]([TransactionDate])
GO
CREATE CLUSTERED INDEX [CX_Sales_CustomerTransactions] ON [Sales].
[CustomerTransactions]
(
[TransactionDate] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF,
DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS =
ON) ON [PS_TransactionDate]([TransactionDate])
GO
表上的非聚集索引是基於CustomerTransactionID,但是分區是基於TransactionDate。同時聚集索引也是TransactionDate列,稱之爲“對齊(align)”
在表中的數據是根據元數據,並且按照分區函數中的範圍來劃分分區。分區函數和分區方案都是獨立的,所以可以被重用,如果你查看[Purchasing].[SupplierTransactions]表,可以看到它和CustomerTransaction使用同樣的分區函數和分區方案。
分區之後,來看看性能表現:
SET STATISTICS IO ON
GO
SET STATISTICS XML ON
GO
SELECT COUNT(*) FROM Sales.CustomerTransactions
WHERE TransactionDate between '2013-01-01' and '2014-01-01'
GO
開啓執行計劃後,我們看一下XML格式的執行計劃:
PartitionAccessed表名訪問了2個分區,然後分別是1和2(看PartitionRange)。這個表名即使執行計劃裏面是掃描,底層執行的時候也不會真的需要訪問全表。
小結
到這裏爲止,我演示了簡單的分區創建和簡單的查詢,主要目的是一種引入,接下來的文章會對分區做一個比較深入的介紹。