SQL Server On Linux(17)—— SQL Server On Linux性能(3)——內置特性(3)——I/O行爲

本人新書上市,請多多關照:《SQL Server On Linux運維實戰 2017版從入門到精通》

在這裏插入圖片描述

對於關係數據庫而言,CPU、內存和I/O子系統都是不可缺少的硬件資源,哪一個出問題都會直接影響性能,不過要注意很多表象,比如CPU利用率高,有時候僅僅是因爲內存不夠,CPU忙於把數據從內存和磁盤之間不停調度導致。所以要堅持從整體去思考的原則。

簡介

  SQL Server藉助read-ahead reading(預讀)、write-ahead logging、checkpoint和數據壓縮等功能,使其在確保數據一致性和持久性的前提下能最大化讀寫性能。另外在Linux平臺還引入了緩存機制,借用O_DIRECT標記來獲取I/O最大化性能。
  接下來對這些功能做下介紹。

Read-Ahead

  SQL Server的數據頁是固定8KB的,如果SQL Server每次讀取一頁時,I/O次數將非常多從而影響性能,因此SQL Server使用了叫read-ahead的機制,把數據頁放入buffer pool。SQL Server認爲當前正在執行的查詢可能會需要這些數據頁,因此把數據預先加載到內存的緩存中會更有效。預讀用於表或索引中分佈在多個頁的數據行的掃描操作。
  可以使用SET STATISTICS IO ON這個T-SQL命令來檢查是否有預讀,也可以用擴展事件跟蹤file_read_completed。還可以使用sys.dm_os_performance_counters這個DMV篩選符合object_name = ‘SQLServer:Buffer Manager’ and counter_name = 'Readahead pages/sec’條件的值。
  但是每次預讀的大小非常依賴於表或索引的組織,因爲它實際上是把數據連帶取出,所以數據應該是要連續的。如果碎片很多,那麼預讀的效果就很不明顯。另外跟SQL Server的版本也有關係,SQL Server 2017企業版最大的單次預讀是1MB,而其他版本則更少。接下來簡單演示一下。

Read-Ahead 演示

  首先創建一個擴展事件跟蹤這個行爲,這裏針對實例數據庫WideWorldImporters進行,這個庫將會多次被使用,如果現在還沒環境可以參考SQL Server On Linux(3)——SQL Server 2019 For Linux 下載並部署示例數據庫

CREATE EVENT SESSION [tracesqlreads] ON SERVER
ADD EVENT sqlserver.file_read_completed(SET collect_path=(1)
    ACTION(sqlserver.database_name,sqlserver.sql_text)
    WHERE ([sqlserver].[database_name]=N'WideWorldImporters'))
ADD TARGET package0.event_file(SET filename=N'tracesqlreads')
WITH (MAX_MEMORY=4096 KB,EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,
MAX_DISPATCH_LATENCY=30 SECONDS,MAX_EVENT_SIZE=0 KB,MEMORY_PARTITION_MODE=NONE,TRACK_CAUSALITY=OFF,STARTUP_STATE=OFF)
GO
ALTER EVENT SESSION [tracesqlreads] ON SERVER STATE=START
GO

  然後執行下面命令,先清空buffer,然後通過SELECT COUNT(*)查詢數據,這個查詢指定了INDEX=1,默認就是聚集索引,這個簡單的操作底層實際上就是把數據從磁盤加載到內存。這裏也使用了SET STATISTICS IO 命令獲取IO的統計信息。

USE [WideWorldImporters]
GO
DBCC DROPCLEANBUFFERS
GO
SET STATISTICS IO ON
GO
SELECT COUNT(*) FROM Sales.Invoices WITH (INDEX=1)
GO

  從下圖可見read-ahead 有11388次
在這裏插入圖片描述

  接下來停止擴展事件並查詢結果:

--停止擴展事件
ALTER EVENT SESSION [tracesqlreads] ON SERVER STATE=STOP
GO

--查詢擴展事件

SELECT [database_name] = xe_file.xml_data.value('(/event/action[@name="database_name"]/value)[1]','[nvarchar](128)'),
[read_size(KB)] = CAST(xe_file.xml_data.value('(/event/data[@name="size"]/value)[1]', '[nvarchar](128)') AS INT)/1024 ,
[file_path] = xe_file.xml_data.value('(/event/data[@name="path"]/value)[1]', '[nvarchar](128)')
--xe_file.xml_data
FROM
(
SELECT [xml_data] = CAST(event_data AS XML)
FROM sys.fn_xe_file_target_read_file('/var/opt/mssql/log/tracesqlreads*.xel', null, null, null)
) AS xe_file
GO

  從下圖結果可以看到實際取數的文件(file_path)和每次讀取的大小(KB),有些文件上只讀取了64KB每次,有些則爲128KB,還有256等等。具體細節我們就不展開討論,但是這裏引出了另外一個最佳實踐裏面的建議,就是對大型數據庫分多個數據文件(可能在同一個文件組也可能在不同文件組),並且分佈在獨立的物理驅動,同時讓這些文件組儘可能初始化大小一致,增長程度一致。目的都是爲了儘可能通過並行讀取接近相同的數據量,一方面加快處理速度,另外一方面使操作不至於失衡。
在這裏插入圖片描述

Write-Ahead Logging

  當SQL Server因爲增刪改等操作需要對buffer中的數據頁進行修改時,如果必須等待每個已修改的數據頁都寫到磁盤才能算結束的話,那麼SQL語句的性能將大大降低。但是由於SQL Server(關係數據庫都一樣)都包含事務日誌文件,所以如果SQL Server能確保將所有的變更先寫入日誌文件,那麼就沒必要馬上寫入數據文件。
  這種機制就叫做write-ahead logging(WAL,暫時稱爲預寫式日誌吧,中文其實不是很重要),WAL其實不是SQL Server的專利,大量的RDBMS(關係數據庫管理系統)都有這個機制,比如Oracle、PG等。SQL Server通過這個機制,確保所有已提交事務的修改都能寫進磁盤的事務日誌文件,所有事務即使在數據文件未更新前均能保證事務的一致性,哪怕SQL Server On Linux/Windows突然出現宕機等情況下。
  事務日誌的寫操作由後臺任務LOG WRITER(可以通過sys.dm_exec_requests篩選LOG WRITER來查看)完成,爲了得到更好的擴展性,從SQL Server 2016開始引入了基於NUMA節點的多個LOG WRITER任務。詳見SQL 2016 – It Just Runs Faster: Multiple Log Writer Workers,比如本系列演示環境:

select session_id, status, command, scheduler_id, task_address, wait_type
from sys.dm_exec_requests
where command = 'LOG WRITER'

在這裏插入圖片描述
  從SQL Server 2014也引入了一個叫delayed durability(延遲持久性)的功能,可以允許在事務未寫入磁盤之前就標誌事務已提交,從而加快事務的持續時間,提高響應速度,但是這種操作明顯會帶來數據丟失的風險。這部分的說明在“控制事務持續性”中可以查看。

Checkpoint,LazyWriters,Eager Writes

Checkpoints

  加載到內存的數據,一旦被修改,就會標誌爲“髒頁(dirty page)”,如果這些頁一直都不移到磁盤做最終保存,那麼事務日誌就一直要記錄這些信息,從而無限制地增長(單純地限制增長也會導致數據庫變成只讀),日誌的增長速度可以非常快,本人見過在幾分鐘之內就會增加幾百G,很容易就導致服務器出現無法預估的空間增長從而最終停止服務。另外,超大的LDF文件也會在數據庫備份和還原過程花費非常多的時間。
  因此,SQL Server提供了一些技術來應對這類問題。默認情況下,SQL Server 2017使用“indirect checkpoint,間接檢查點”來寫入數據庫頁。它使用目標恢復時間(target recovery time)來決定什麼時候獲取髒頁信息並寫到對應的數據庫數據文件中。這個目標恢復時間可以使用ALTER DATABASE TARGET_RECOVERY_TIME命令來配置,默認值爲1分鐘。
  SQL Server還有一個automatic checkpoints的選項,這是SQL 2016之前的默認功能,使用sp_configure配置recovery interval來實現。但是在運行時可能會導致I/O寫行爲的密集出現。自動檢查點使用後臺任務CHECKPOINT來實現,(從sys.dm_exec_requests where command =‘RECOVERY WRITER’查看)。間接檢查點的出現就是爲了儘可能減少這種峯值的影響,它使用後臺任務“RECOVERY WRITER”(從sys.dm_exec_requests where command =‘RECOVERY WRITER’查看)來進行操作。
  除了上面兩種主要的ckeckpoint類型之外,SQL Server還會針對某些特定的操作和事件觸發checkpoint,完整的內容可以查閱官方文檔:數據庫檢查點 (SQL Server)。如果要監控checkpoint,可以查看sys.dm_os_performance_counter 中object_name=’ SQLServer:Buffer Manager’ and counter_name=‘Background writer pages/sec’ (針對間接檢查點)和查看sys.dm_os_performance_counter 中object_name=’ SQLServer:Buffer Manager’ and counter_name=‘Checkpoint pages/sec’ (針對自動檢查點)。

Lazywrite

  由於絕大部分服務器上的數據庫體積都遠大於物理內存的數量,所以內存無法真正緩存所有的數據。在系統運行的過程中,經常會出現所需的數據沒有在緩存中,需要從硬盤上加載,但是緩存本身已經沒有足夠的空間,這個時候就需要吧緩存中的某些數據清空回去硬盤,首當其衝的就是那些在緩存中已被修改過的數據頁(髒頁),SQL Server使用WAL來進行這種操作以便保證數據的一致性,但是有些髒頁還在未提交的事務中,它們可能在後續會被回滾,爲了保證這種邏輯,在髒頁寫入之前,事務日誌需要先記錄。在所有buffer pool管理動作中寫入髒頁的操作統稱lazywrite。通常由稱爲LAZY WRITER的後臺進程執行(sys.dm_exec_requests where command=‘LAZY WRITER’)。
  但是如果出現短時大容量數據變更,如SELECT …INTO/INSERT…SELECT等,使用lazywrite效率很低。這個時候SQL Server會使用Eager write(官網翻譯爲勤奮寫入)來實現髒頁寫入。這種操作適合上面所述的屬於大容量日誌操作關聯的髒頁。以並行方式創建和寫入新頁,不需要等待整個操作完成就可以將頁寫入磁盤。

小結

  檢查點(checkpoint)、惰性寫入(lazywrite)和勤奮寫入(eager write)是三種髒頁寫入磁盤的方式。它們均不需要等待I/O操作完成,始終使用異步I/O實現。可以充分利用服務器的CPU和I/O資源。

數據壓縮

  對於B-tree索引而言,索引的層級直接影響索引效率,而層級的大小又跟數據頁的數量有關係,如果每個數據頁能容納更多的數據,那麼相同的數據就只需要更少的數據頁從而提升索引性能,另外在數據庫常規維護過程中,數據體積越小越好,這個就沒必要多說了。SQL Server 從2008版開始引入了數據壓縮技術,包含頁壓縮和行壓縮,使得更少的數據頁需要加載並駐留在buffer中,這個已經是很成熟的功能,在後續章節會提到另外一種壓縮,就是使用列存儲索引。但是注意壓縮和解壓由於需要額外的CPU進行計算,所以並不是所有地方都通用的。讀者可以先行閱讀Data Compression。這裏先不多說。

總結

  本文從SQL Server內部介紹了SQL Server關於I/O方面的特性。有些是SQL Server 2016開始引入的新特性,有些則是出現時間較久但是依舊有效的特性。下一篇將從配置層面去介紹如何最大化SQL Server特別是Linux平臺的性能。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章