sqlserver表分区

表分区是提升 SQL Server 数据库性能、提高维护效率的重要手段,尤其在处理大数据量时更显著。在设计和实施分区策略时,建议结合具体的业务场景和数据需求,以实现最佳效果。

概述

什么是表分区:

  • 表分区是将单个逻辑表分散到多个物理存储位置的过程。SQL Server 支持多种分区方法,包括基于id范围、日期、列表、哈希等。每个分区可以被独立管理,从而优化查询性能和维护后台操作。
  • 对表进行分区的过程,实际上是将逻辑上完整的一个表,按照特定的字段拆分成多个分区,分散到相同或不同的FileGroup中,每一个部分叫做表的一个分区,一个分区实际上是一个独立的物理表。也就是说,分区表在逻辑上是一个表,而在物理上是多个完全独立的表。

表分区的优点:

  • 1.提高查询性能。当查询涉及到大数据量时,全表扫描会消耗大量资源。分区可以限制这些查询只在相关的分区上进行,从而大幅提高查询效率。
  • 2.方便数据维护。表分区使数据清理和归档变得更简单。可以独立处理每个分区,如直接删除或修剪过期数据,不影响其他分区的数据。
--.将特定分区切换到备份表
ALTER TABLE Log_UserVisit SWITCH PARTITION 2 TO Log_UserVisit_20130416
  • 3.提高并发处理能力。分区数据可以在多个文件组中存储,从而允许多个用户或进程并行访问不同的分区。这增强了系统的处理能力,并提高了响应速度。
  • 4.允许精细的索引策略。对于每个分区,可以实施不同的索引策略,以满足特定的查询需求。这不仅优化了存储,还有助于提升查询性能。

配置表分区实战

  • 示例:磁盘0划分C盘、D盘,磁盘1划分E盘,磁盘2划分F盘,使用2个磁盘划分4个文件组(有条件的话,建议每个文件组使用单独的磁盘)
配置项 配置名称 备注
机器名 win2019 Windows Server 2019 Datacenter x64
SQL Server 版本 15.0.2000.5 SQL Server 2019 (RTM) Enterprise x64
数据目录 D:\sqldata\data 磁盘0
日志目录 D:\sqldata\log 磁盘0
文件组1 E:\sqldata\filegroup1 磁盘1
文件组2 F:\sqldata\filegroup2 磁盘2
文件组3 E:\sqldata\filegroup3 磁盘1
文件组4 F:\sqldata\filegroup4 磁盘2

1. 创建数据库

  • 示例:数据目录及日志目录均在D盘(磁盘0)
USE [master]
GO

IF EXISTS (SELECT name FROM sys.databases WHERE name='MyTestDB')
DROP DATABASE MyTestDB
GO

--.创建数据库目录
EXEC xp_create_subdir 'D:\sqldata\data'
EXEC xp_create_subdir 'D:\sqldata\log'

--.创建数据库
CREATE DATABASE MyTestDB ON PRIMARY ( 
  NAME = N'MyTestDB', 
  FILENAME = N'D:\sqldata\data\MyTestDB.mdf' , 
  SIZE = 100MB , 
  MAXSIZE = UNLIMITED, 
  FILEGROWTH = 10MB )
LOG ON ( 
  NAME = N'MyTestDB_log', 
  FILENAME = N'D:\sqldata\log\MyTestDB_log.ldf' , 
  SIZE = 1024MB , 
  MAXSIZE = 128GB , 
  FILEGROWTH = 10%)
GO

2. 添加文件组

  • 示例:4个文件组分布在磁盘1、磁盘2,依次为E盘、F盘、E盘、F盘(有条件的话,建议每个文件组使用单独的磁盘)
USE [master]
GO

--.创建数据库目录
EXEC xp_create_subdir 'E:\sqldata\filegroup1'
EXEC xp_create_subdir 'F:\sqldata\filegroup2'
EXEC xp_create_subdir 'E:\sqldata\filegroup3'
EXEC xp_create_subdir 'F:\sqldata\filegroup4'

--.添加文件组
ALTER DATABASE MyTestDB ADD FILEGROUP fg_1
ALTER DATABASE MyTestDB ADD FILEGROUP fg_2
ALTER DATABASE MyTestDB ADD FILEGROUP fg_3
ALTER DATABASE MyTestDB ADD FILEGROUP fg_4

--.添加数据库文件到指定的文件组
ALTER DATABASE MyTestDB ADD FILE ( 
  NAME = N'MyTestDB_f1', 
  FILENAME = N'E:\sqldata\filegroup1\MyTestDB_f1.ndf' , 
  SIZE = 100MB, 
  MAXSIZE = 102400MB, 
  FILEGROWTH = 10MB ) TO FILEGROUP fg_1
GO

ALTER DATABASE MyTestDB ADD FILE ( 
  NAME = N'MyTestDB_f2', 
  FILENAME = N'F:\sqldata\filegroup2\MyTestDB_f2.ndf' , 
  SIZE = 100MB, 
  MAXSIZE = 102400MB, 
  FILEGROWTH = 10MB ) TO FILEGROUP fg_2
GO

ALTER DATABASE MyTestDB ADD FILE (
  NAME = N'MyTestDB_f3', 
  FILENAME = N'E:\sqldata\filegroup3\MyTestDB_f3.ndf' , 
  SIZE = 100MB, 
  MAXSIZE = 102400MB, 
  FILEGROWTH = 10MB ) TO FILEGROUP fg_3
GO

ALTER DATABASE MyTestDB ADD FILE (
  NAME = N'MyTestDB_f4', 
  FILENAME = N'F:\sqldata\filegroup4\MyTestDB_f4.ndf' , 
  SIZE = 100MB, 
  MAXSIZE = 102400MB, 
  FILEGROWTH = 10MB ) TO FILEGROUP fg_4
GO
  • 示例db如下:

3. 配置表分区

USE MyTestDB
GO

--.1.清理测试痕迹
IF OBJECT_ID('Log_UserVisit') IS NOT NULL
DROP TABLE Log_UserVisit
GO

IF EXISTS (SELECT [name] FROM sys.partition_schemes WHERE [name] = 'ps_LogUserVisit_date')
DROP PARTITION SCHEME ps_LogUserVisit_date
GO

IF EXISTS (SELECT [name] FROM sys.partition_functions WHERE [name] = 'pfRight_LogUserVisit_date')
DROP PARTITION FUNCTION pfRight_LogUserVisit_date
GO

--.2.创建分区函数
CREATE PARTITION FUNCTION pfRight_LogUserVisit_date(DATETIME)
AS RANGE RIGHT
FOR VALUES
(                   --(更早, 2012/12/01]
  '2012-12-01',     --[2012/12/01,2012/12/24)
  '2012-12-24',     --[2012/12/24,2012/12/31)
  '2012-12-31'      --[2012/12/31,至今)
)
GO

--.3.创建分区方案,关联到分区函数
CREATE PARTITION SCHEME ps_LogUserVisit_date
AS PARTITION pfRight_LogUserVisit_date
TO (fg_1,fg_2,fg_3,fg_4)   --警告:N个临界值就要有N+1个文件组
-- ALL TO ([PRIMARY])
GO

--.4.创建测试表
CREATE TABLE Log_UserVisit(
  [UserID] [varchar](50) NOT NULL,
  [UserType] [int] NOT NULL,
  [InsertTime] [datetime] NOT NULL,
  [IPAddress] [varchar](50) NOT NULL)
GO

--.5.添加分区索引,并将其绑定到分区方案上
ALTER TABLE Log_UserVisit
  ADD CONSTRAINT PK_Log_UserVisit_Date PRIMARY KEY CLUSTERED (InsertTime)
  ON ps_LogUserVisit_date(InsertTime)
GO

--.针对老表添加分区索引
-- CREATE NONCLUSTERED INDEX IX_Log_UserVisit_Date ON Log_UserVisit(InsertTime) ON ps_LogUserVisit_date(InsertTime)

4. 测试验证

--.1.新增测试数据
INSERT INTO Log_UserVisit
        SELECT 'zhang1',0,'2011-10-01 18:59:09','192.168.0.1'
  UNION SELECT 'zhang2',0,'2012-12-04 19:59:09','192.168.0.2'
  UNION SELECT 'zhang3',0,'2012-12-28 19:59:09','192.168.0.3'
  UNION SELECT 'zhang4',0,'2012-12-31 19:59:09','192.168.0.4'
  UNION SELECT 'zhang5',0,'2013-05-01 19:59:09','192.168.0.5'

--.2.查询该表是不是分区成功
SELECT * FROM sys.partition_range_values
SELECT $partition.pfRight_LogUserVisit_date(GETDATE())
SELECT * FROM SYS.PARTITIONS WHERE OBJECT_ID = OBJECT_ID('Log_UserVisit')

--.3.查询指定分区的数据
SELECT $partition.pfRight_LogUserVisit_date(InsertTime) AS 'Partition',*
FROM Log_UserVisit
WHERE $partition.pfRight_LogUserVisit_date(InsertTime)=1

--.4.统计每个分区有多少数据
SELECT $partition.pfRight_LogUserVisit_date(InsertTime) AS 'Partition',COUNT(*) AS 'cunt'
FROM Log_UserVisit
GROUP BY $partition.pfRight_LogUserVisit_date(InsertTime)
ORDER BY $partition.pfRight_LogUserVisit_date(InsertTime)

表分区维护

1. 对特定分区进行维护

--.对特定分区进行索引重建
ALTER INDEX PK_Log_UserVisit_Date ON Log_UserVisit REBUILD PARTITION = 2

2. split 添加分区

  • 示例:添加分区,使其包含大于 '2013-04-01'
--.1.创建目录
EXEC xp_create_subdir 'D:\sqldata\filegroup5'

--.2.添加新的文件组
ALTER DATABASE MyTestDB ADD FILEGROUP fg_5

--.3.添加db文件到新的文件组
ALTER DATABASE MyTestDB ADD FILE (
  NAME = N'MyTestDB_f5', 
  FILENAME = N'D:\sqldata\filegroup5\MyTestDB_f5.ndf' , 
  SIZE = 100MB, 
  MAXSIZE = 102400MB, 
  FILEGROWTH = 10MB ) TO FILEGROUP fg_5

--.4.调整分区方案
ALTER PARTITION SCHEME ps_LogUserVisit_date NEXT USED fg_5

--.5.调整分区函数
ALTER PARTITION FUNCTION pfRight_LogUserVisit_date() SPLIT RANGE('2013-04-01')

--.确认:统计每个分区有多少数据
SELECT $partition.pfRight_LogUserVisit_date(InsertTime) AS 'Partition',COUNT(*) AS 'cunt'
FROM Log_UserVisit
GROUP BY $partition.pfRight_LogUserVisit_date(InsertTime)
ORDER BY $partition.pfRight_LogUserVisit_date(InsertTime)

3. merge 合并分区

  • 去除一个边界值,实现分区合并
--.调整分区函数
ALTER PARTITION FUNCTION pfRight_LogUserVisit_date() MERGE RANGE('2013-04-01')

4. switch 切换分区

  • 场景1:切换源表的指定分区 to 目标分区表的指定分区(表结构应与分区表的一致),然后再切回源分区表
--.1.创建目标分区表
IF OBJECT_ID('Log_UserVisit_Bak') IS NOT NULL
DROP TABLE Log_UserVisit_Bak
GO

CREATE TABLE Log_UserVisit_Bak(
  [UserID] [varchar](50) NOT NULL,
  [UserType] [int] NOT NULL,
  [InsertTime] [datetime] NOT NULL,
  [IPAddress] [varchar](50) NOT NULL)
GO

ALTER TABLE Log_UserVisit_Bak
  ADD CONSTRAINT PK_Log_UserVisit_Bak_Date PRIMARY KEY CLUSTERED (InsertTime)
  ON ps_LogUserVisit_date(InsertTime)
GO

--.2.先切换到备份表
ALTER TABLE Log_UserVisit SWITCH PARTITION 1 TO Log_UserVisit_Bak PARTITION 1 
ALTER TABLE Log_UserVisit SWITCH PARTITION 2 TO Log_UserVisit_Bak PARTITION 2 

--.3.统计每个分区有多少数据,针对 Log_UserVisit
SELECT $partition.pfRight_LogUserVisit_date(InsertTime) AS 'Partition',COUNT(*) AS 'cunt'
FROM Log_UserVisit
GROUP BY $partition.pfRight_LogUserVisit_date(InsertTime)
ORDER BY $partition.pfRight_LogUserVisit_date(InsertTime)

--.3.统计每个分区有多少数据,针对 Log_UserVisit_Bak
SELECT $partition.pfRight_LogUserVisit_date(InsertTime) AS 'Partition',COUNT(*) AS 'cunt'
FROM Log_UserVisit_Bak
GROUP BY $partition.pfRight_LogUserVisit_date(InsertTime)
ORDER BY $partition.pfRight_LogUserVisit_date(InsertTime)


--.回滚:再切换到原表
ALTER TABLE Log_UserVisit_Bak SWITCH PARTITION 1 TO Log_UserVisit PARTITION 1 
ALTER TABLE Log_UserVisit_Bak SWITCH PARTITION 2 TO Log_UserVisit PARTITION 2 
GO
  • 场景2:切换源表的指定分区 to 普通表(表结构应与分区表的一致),然后再切回源分区表
--.1.创建目标表
IF OBJECT_ID('Log_UserVisit_Part2') IS NOT NULL
DROP TABLE Log_UserVisit_Part2
GO

--.2.警告:普通表必须建立在分区表切换分区所在的文件组上,否则 SWITCH PARTITION 会报错
CREATE TABLE Log_UserVisit_Part2(
  [UserID] [varchar](50) NOT NULL,
  [UserType] [int] NOT NULL,
  [InsertTime] [datetime] NOT NULL,
  [IPAddress] [varchar](50) NOT NULL)
ON fg_2   --.第4步示例的 PARTITION 2 在文件组 fg_2 上

--.3.警告:普通表上的索引要跟分区表一致,否则 SWITCH PARTITION 会报错
ALTER TABLE Log_UserVisit_Part2 ADD CONSTRAINT PK_Log_UserVisit_Part2_Date PRIMARY KEY CLUSTERED (InsertTime)

--.4.将特定分区切换到普通表(目标表必须是空表,不能有任何数据)
ALTER TABLE Log_UserVisit SWITCH PARTITION 2 TO Log_UserVisit_Part2

--.确认:统计每个分区有多少数据
SELECT $partition.pfRight_LogUserVisit_date(InsertTime) AS 'Partition',COUNT(*) AS 'cunt'
FROM Log_UserVisit
GROUP BY $partition.pfRight_LogUserVisit_date(InsertTime)
ORDER BY $partition.pfRight_LogUserVisit_date(InsertTime)


--.回滚:切换普通表数据到分区表的特定分区中
--.警告:普通表必须要有和分区数据范围一致的 check 约束条件,否则 SWITCH TO 会报错
ALTER TABLE Log_UserVisit_Part2 ADD CONSTRAINT CK_Log_UserVisit_Part2_Date check (InsertTime>='2012-12-01' and InsertTime<'2012-12-24')
ALTER TABLE Log_UserVisit_Part2 SWITCH TO Log_UserVisit PARTITION 2

附录

1. 单块磁盘配置表分区也能提高性能吗?

多块磁盘配置表分区可以提升io并发性能,而在单块磁盘配置下使用表分区,受限于物理I/O瓶颈,性能提升的效果确实有限,但仍可能有以下方面的好处:

  • 1、查询时如果使用了分区键条件,引擎可以只扫描相关分区,减少I/O量。对大型表特别有效,即使在同一磁盘上
  • 2、可以单独对某个分区进行维护(如重建索引、统计信息更新)。对存在大量历史数据的表,可以只对最新数据所在的分区(而非全表)进行索引重建
  • 3、数据归档时可以直接切换分区而非删除大量数据。大表删数据的效率太慢且影响业务
  • 4、分区表更易于实现并行查询计划

2. 单个文件组如何配置表分区?

  • 如果不便使用多个文件组来配置表分区,请在创建分区方案的时候指定 ALL TO ([PRIMARY]),如下示例:
USE MyTestDB
GO

--.1.创建分区函数
CREATE PARTITION FUNCTION pfRight_LogUserVisit_date(DATETIME) AS RANGE RIGHT FOR VALUES ('2012-12-01','2012-12-24','2012-12-31')

--.2.创建分区方案,关联到分区函数
CREATE PARTITION SCHEME ps_LogUserVisit_date AS PARTITION pfRight_LogUserVisit_date ALL TO ([PRIMARY])

--.3.创建测试表
CREATE TABLE Log_UserVisit(UserID varchar(50) NOT NULL, UserType int NOT NULL, InsertTime datetime NOT NULL)

--.4.添加分区索引,并将其绑定到分区方案上
ALTER TABLE Log_UserVisit ADD CONSTRAINT PK_Log_UserVisit_Date PRIMARY KEY CLUSTERED (InsertTime) ON ps_LogUserVisit_date(InsertTime)
Copyright © www.sqlfans.cn 2021 - 2026 All Right Reserved更新时间: 2026-09-08 14:42:06

results matching ""

    No results matching ""