Sql Server水平分区
什么是分区
表数据按照你指定的规则分放到不同的文件里,把一个大的数据文件拆分为多个小文件,还可以把这些小文件放在不同的磁盘下由多个cpu进行处理。这样文件的大小随着拆分而减小,还得到硬件系统的加强,自然对我们操作数据是大大有利的。
所以大数据量的数据表,对分区的需要还是必要的,因为它可以提高select效率,还可以对历史数据经行区分存档等。但是数据量少(3000w以下)就不要凑这个热闹啦,因为表分区会对数据库产生不必要的开销,除了性能还会增加实现对象的管理费用和复杂性。
什么是分表
把一张表按一定的规则分解成N个具有独立存储空间的实体表。系统读写时需要根据定义好的规则得到对应的表名,然后操作它。分表的实现方式比较复杂,需要修改源程序代码,会给开发带来大量工作,极大的增加了开发成本。只适合在开发初期就考虑到了大量数据存在,做好了分表处理,不适合应用上线了再做修改,成本太高。
什么是分库
分表能够解决单表数据量过大带来的查询效率下降的问题,但是却无法给数据库的并发处理能力带来质的提升。面对高并发的读写访问,当数据库Master服务器无法承载写操作压力时,不管如何扩展Slave服务器,都没有什么意义了。因此必须换一种思路:既然拆分多个表不行了,那就拆分多个数据库好了,聚集多个单数据库的写入能力提高整体的写入能力,这就是分库的概念。做分库会带来大量的开发成本,得不偿失!不推荐使用。
怎么选择
当访问量不大,但单表数据量比较多,查询效率低,可以只进行分区。
当访问量大,且单表数据量比较多,查询效率低,可以分区和分表搭配使用。
当需要面对高并发的读写访问时,写入效率低,可以进行分库。
概念理解
1.分区函数:指定分区数据范围规则,分区数量,然后将数据映射到一组分区上。
2.分区方案:指定分区对应的文件组。
一个文件组可以由多个分区使用,但是文件组和分区1:1对应效率最高。
一个分区方案只能使用一个分区函数,一个分区函数可以用于多个分区方案,但是建议1:1。
表做完分区之后,如果想在添加新分区,只能修改分区函数和分区方案,不能再新建新的分区函数和分区方案。
跟着做,分区如此简单
分区是要把一个表数据拆分为若干子集合,也就是把一个数据文件拆分到多个数据文件中,然而这些文件的存放可以依托一个文件组或这多个文件组,由于多个文件组可以提高数据库的访问并发量,还可以把不同的分区配置到不同的磁盘中提高效率,所以创建时建议分区跟文件组个数相同。
使用文件和文件组时的建议:
- 大多数数据库在只有单个数据文件和单个事务日志文件的情况下性能良好。
- 如果使用多个数据文件,请为附加文件创建第二个文件组,并将其设置为默认文件组。 这样,主文件将只包含系统表和对象。
- 若要使性能最大化,请在尽可能多的不同可用磁盘上创建文件或文件组。 将争夺空间最激烈的对象置于不同的文件组中。
- 将在同一联接查询中使用的不同表置于不同的文件组中。 由于采用并行磁盘 I/O 对联接数据进行搜索,所以此步骤可改善性能。
- 将最常访问的表和属于这些表的非聚集索引置于不同的文件组中。 如果文件位于不同的物理磁盘上,由于采用并行 I/O,所以使用不同的文件组可改善性能。
- 请勿将事务日志文件置于已有其他文件和文件组的同一物理磁盘上。
1.创建文件组
可以点击数据库属性在文件组里面添加。
T-sql语法:
alter database <数据库名> add filegroup <文件组名>
---创建数据库文件组 alter database testSplit add filegroup ByIdGroup1 alter database testSplit add filegroup ByIdGroup2 alter database testSplit add filegroup ByIdGroup3 alter database testSplit add filegroup ByIdGroup4 alter database testSplit add filegroup ByIdGroup5 alter database testSplit add filegroup ByIdGroup6 alter database testSplit add filegroup ByIdGroup7 alter database testSplit add filegroup ByIdGroup8 alter database testSplit add filegroup ByIdGroup9 alter database testSplit add filegroup ByIdGroup10
2.创建数据文件到文件组里面
可以点击数据库属性在文件里面添加
T-sql语法:
alter database <数据库名称> add file <数据标识> to filegroup <文件组名称> --<数据标识> (name:文件名,fliename:物理路径文件名,size:文件初始大小kb/mb/gb/tb,filegrowth:文件自动增量kb/mb/gb/tb/%,maxsize:文件可以增加到的最大大小kb/mb/gb/tb/unlimited)
alter database testSplit add file (name=N'ById1',filename=N'J:\Work\数据库\data\ById1.ndf',size=5Mb,filegrowth=5mb) to filegroup ByIdGroup1 alter database testSplit add file (name=N'ById2',filename=N'J:\Work\数据库\data\ById2.ndf',size=5Mb,filegrowth=5mb) to filegroup ByIdGroup2 alter database testSplit add file (name=N'ById3',filename=N'J:\Work\数据库\data\ById3.ndf',size=5Mb,filegrowth=5mb) to filegroup ByIdGroup3 alter database testSplit add file (name=N'ById4',filename=N'J:\Work\数据库\data\ById4.ndf',size=5Mb,filegrowth=5mb) to filegroup ByIdGroup4 alter database testSplit add file (name=N'ById5',filename=N'J:\Work\数据库\data\ById5.ndf',size=5Mb,filegrowth=5mb) to filegroup ByIdGroup5 alter database testSplit add file (name=N'ById6',filename=N'J:\Work\数据库\data\ById6.ndf',size=5Mb,filegrowth=5mb) to filegroup ByIdGroup6 alter database testSplit add file (name=N'ById7',filename=N'J:\Work\数据库\data\ById7.ndf',size=5Mb,filegrowth=5mb) to filegroup ByIdGroup7 alter database testSplit add file (name=N'ById8',filename=N'J:\Work\数据库\data\ById8.ndf',size=5Mb,filegrowth=5mb) to filegroup ByIdGroup8 alter database testSplit add file (name=N'ById9',filename=N'J:\Work\数据库\data\ById9.ndf',size=5Mb,filegrowth=5mb) to filegroup ByIdGroup9 alter database testSplit add file (name=N'ById10',filename=N'J:\Work\数据库\data\ById10.ndf',size=5Mb,filegrowth=5mb) to filegroup ByIdGroup10
执行完成后,右键数据库看文件组跟文件里面是不是多出来啦这些文件组跟文件。
3.使用向导创建分区表
右键到要分区的表--- >> 存储 --- >> 创建分区 --- >>显示向导视图 --- >> 下一步 --- >> 下一步。。
这里举例说下选择列的意思:
假如你选择的是int类型的列:那么你的分区可以指定为1--100W是一个分区,100W--200W是一个分区....
假如你选择的是datatime类型:那么你的分区可以指定为:2014-01-01--2014-01-31一个分区,2014-02-01--2014-02-28一个分区...
根据这样的列数据规则划分,那么在那个区间的数据,在插入数据库时就被指向那个分区存储下来。
我这里选用orderid int类型 --- >> 下一步 --- >>
左边界右边界:就是把临界值划分给上一个分区还是下一个分区。一个小于号,一个小于等于号。注意最后一个分区的边界空着不填。
点击预计存储空间,会计算出每个分区有多少行,占用空间等。然后下一步下一步最后你会得到分区函数和分区方案。
USE [testSplit] GO BEGIN TRANSACTION --创建分区函数 CREATE PARTITION FUNCTION [bgPartitionFun](int) AS RANGE LEFT FOR VALUES (N'1000000', N'2000000', N'3000000', N'4000000', N'5000000', N'6000000', N'7000000', N'8000000', N'9000000', N'10000000') --创建分区方案 CREATE PARTITION SCHEME [bgPartitionSchema] AS PARTITION [bgPartitionFun] TO ([PRIMARY], [ByIdGroup1], [ByIdGroup2], [ByIdGroup3], [ByIdGroup4], [ByIdGroup5], [ByIdGroup6], [ByIdGroup7], [ByIdGroup8], [ByIdGroup9], [ByIdGroup10]) --创建分区索引 CREATE CLUSTERED INDEX [ClusteredIndex_on_bgPartitionSchema_635342971076448165] ON [dbo].[BigOrder] ( [OrderId] )WITH (SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF) ON [bgPartitionSchema]([OrderId]) --删除分区索引 DROP INDEX [ClusteredIndex_on_bgPartitionSchema_635342971076448165] ON [dbo].[BigOrder] WITH ( ONLINE = OFF ) COMMIT TRANSACTION
执行上面向导生成的语句。分区完成。。
4.秀一下速度。
首先我在表中插入啦1千万行数据。给表分啦11个分区。前十个分区里面一个是100W条数据。。
说两句:
可见反常现象,扫描次数跟逻辑读取次数都是无分区表的2倍之多,但查询速度却是快啦不少啊。这就是分区的神奇之处啊,所以要相信这世界一切皆有可能。
分区函数,分区方案,分区表,分区索引
1.分区函数
指定分依据区列(依据列唯一),分区数据范围规则,分区数量,然后将数据映射到一组分区上。
创建语法:
create partition function 分区函数名(<分区列类型>) as range [left/right] for values (每个分区的边界值,....)
--创建分区函数 CREATE PARTITION FUNCTION [bgPartitionFun](int) AS RANGE LEFT FOR VALUES (N'1000000', N'2000000', N'3000000', N'4000000', N'5000000', N'6000000', N'7000000', N'8000000', N'9000000', N'10000000')
然而,分区函数只定义了分区的方法,此方法具体用在哪个表的那一列上,则需要在创建表或索引是指定。
删除语法:
--删除分区语法 drop partition function <分区函数名>
--删除分区函数 bgPartitionFun drop partition function bgPartitionFun
2.分区方案
指定分区对应的文件组。
创建语法:
--创建分区方案语法 create partition scheme <分区方案名称> as partition <分区函数名称> [all]to (文件组名称,....)
--创建分区方案,所有分区在一个组里面 CREATE PARTITION SCHEME [bgPartitionSchema] AS PARTITION [bgPartitionFun] TO ([ByIdGroup1], [ByIdGroup1], [ByIdGroup1], [ByIdGroup1], [ByIdGroup1], [ByIdGroup1], [ByIdGroup1], [ByIdGroup1], [ByIdGroup1], [ByIdGroup1], [ByIdGroup1])
分区函数必须关联分区方案才能有效,然而分区方案指定的文件组数量必须与分区数量一致,哪怕多个分区存放在一个文件组中。
删除语法:
--删除分区方案语法 drop partition scheme<分区方案名称>
--删除分区方案 bgPartitionSchema drop partition scheme bgPartitionSchema1
只有没有分区表,或索引使用该分区方案时,才能对其删除。
另外删除的时候的注意点:
如果想要删除分组呢就要先删除分组中的数据文件,想要删除分区函数就要先删除分区方案,要删除分区方案就要先删除应用的数据表。
3.分区表
创建语法:
--创建分区表语法 create table <表名> ( <列定义> )on<分区方案名>(分区列名)
--创建分区表 create table BigOrder ( OrderId int identity, orderNum varchar(30) not null, OrderStatus int not null default 0, OrderPayStatus int not null default 0, UserId varchar(40) not null, CreateDate datetime null default getdate(), Mark nvarchar(300) null )on bgPartitionSchema(OrderId)
如果在表中创建主键或唯一索引,则分区依据列必须为该列。
4.分区索引
创建语法:
--创建分区索引语法 create <索引分类> index <索引名称> on <表名>(列名) on <分区方案名>(分区依据列名)
--创建分区索引 CREATE CLUSTERED INDEX [ClusteredIndex_on_bgPartitionSchema_635342971076448165] ON [dbo].[BigOrder] ( [OrderId] )WITH (SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF) ON [bgPartitionSchema]([OrderId])
使用分区索引查询,可以避免多个cpu操作多个磁盘时产生的冲突。
分区表明细信息
这里的语法,我就不写啦,自己看语句分析吧。简单的很。。
1.查看分区依据列的指定值所在的分区
--查询分区依据列为10000014的数据在哪个分区上 select $partition.bgPartitionFun(2000000) --返回值是2,表示此值存在第2个分区
2.查看分区表中,每个非空分区存在的行数
--查看分区表中,每个非空分区存在的行数 select $partition.bgPartitionFun(orderid) as partitionNum,count(*) as recordCount from bigorder group by $partition.bgPartitionFun(orderid)
3.查看指定分区中的数据记录
---查看指定分区中的数据记录 select * from bigorder where $partition.bgPartitionFun(orderid)=2
结果:数据从1000001开始到200W结束
4.查看分区方案、分区函数和分区界限
select * from sys.partition_schemes
select function_id from sys.partition_functions
select * from sys.partition_range_values
分区的拆分与合并以及切换
1.拆分分区
首先新建1个文件组和文件,在原分区方案中指定新的文件组。然后在分区函数中新增一个边界值,即可将一个分区变为2个。
--修改分区方案,指定新的文件组
alter partition scheme 分区方案 next used 新文件组名
--修改分区函数,增加分区边界 alter partition function 分区函数() split range(N'2015-11-08 00:00:00')
注意:如果分区函数已经指定了分区方案,则分区数需要和分区方案中指定的文件组个数保持对应一致。
2.合并分区
与拆分分区相反,去除一个边界值即可。
--合并分区 alter partition function 分区函数() merge range(N'1500000') --将边界值两边的分区合并
3.分区切换
切换分区需要符合以下要求:
1、原表与目标表结构必须相同,且必须在同一个文件组中。
2、目标表或者分区必须是空的。
3、若源表存在聚集分区索引,则要求目标表也需要有同样的聚集分区索引。
4、源表的所有索引必须与源表分区对齐。
5、若目标表存在索引,约束,外键,要求源表必须与目标表相同结构的索引,约束,外键。
6、目标表和源表都不能存在复制分发。
7、分区依据列是否可为空的约束必须相同。
一般切换分区可以做如下处理:
-
将现有表作为分区分配到现有的已分区表。
ALTER TABLE [dbname].[dbo].[tablename] SWITCH TO [dbname].[dbo].[staging_tablename] PARTITION source_partition_number_expression
-
将分区从一个已分区表切换到另外一个已分区表中。
ALTER TABLE [dbname].[dbo].[tablename] SWITCH PARTITION source_partition_number_expression TO [dbname].[dbo].[staging_tablename] PARTITION source_partition_number_expression
-
将一个分区切换到现有表中。
ALTER TABLE [dbname].[dbo].[tablename] SWITCH PARTITION source_partition_number_expression TO [dbname].[dbo].[staging_tablename]
自动分区测试脚本
可以采用SQL Server代理中的作业定期自动执行分区脚本,实现自动分区(如每月自动执行按月分区的操作)
DECLARE @fileGroupName VARCHAR(20), --文件组名:FG+年份+数字 @fileName VARCHAR(20), --文件名:F+年份+数字 @filePath VARCHAR(100),--文件路径 @dataBaseName VARCHAR(20),--数据库名 @Year CHAR(4), @schemeName VARCHAR(20),--分区方案名 @partFunctionName VARCHAR(20),--分区函数名 @limit VARCHAR(100) --分区界限,以时间分区,yyyy-mm-dd SET @Year=YEAR(GETDATE()) SET @fileGroupName='FG-'+@Year+ CASE WHEN MONTH(GETDATE()) IN ('1','2') THEN '-1' WHEN MONTH(GETDATE()) IN ('3','4') THEN '-2' WHEN MONTH(GETDATE()) IN ('5','6') THEN '-3' WHEN MONTH(GETDATE()) IN ('7','8') THEN '-4' WHEN MONTH(GETDATE()) IN ('9','10') THEN '-5' WHEN MONTH(GETDATE()) IN ('11','12') THEN '-6' END SET @fileName=REPLACE(@fileGroupName,'G','') SET @filePath='D:\Program Files\Microsoft SQL Server\MSSQL14.MSSQLSERVER\MSSQL\DATA\'+@fileName+'.ndf' SET @dataBaseName='TSQL2012' SET @schemeName='scheme_orders' SET @partFunctionName='function_orders' SET @limit=CONVERT(varchar(100),GETDATE(),23) --新增文件组 if exists(select * from TSQL2012.sys.filegroups where name=@fileGroupName) begin print '文件组存在,不需添加' end else begin exec('ALTER DATABASE TSQL2012 ADD FILEGROUP ['+@fileGroupName+']') end --添加文件 if exists(select * from TSQL2012.sys.database_files where state=0 and (name=@fileName or physical_name=@filePath)) begin print '文件存在,不需添加' end else begin exec('ALTER DATABASE TSQL2012 ADD FILE(NAME ='''+@fileName+''',FILENAME = '''+@filePath+''')TO FILEGROUP ['+@fileGroupName+']') END --修改分区方案 if exists(select * from sys.partition_schemes where name=@schemeName) begin exec('alter partition scheme '+@schemeName+' next used ['+@fileGroupName+']') end else begin print '分区方案不存在' END --修改分区函数,添加界限 if exists(select * from sys.partition_range_values where function_id=(select function_id from sys.partition_functions where name=@partFunctionName)) begin if exists(select * from sys.partition_range_values where function_id=(select function_id from sys.partition_functions where name=@partFunctionName) and CONVERT(datetime,value,23)=@limit) begin print '界限已存在' end else begin exec('alter partition function '+@partFunctionName+'() split range('''+@limit+''')') end end else begin print '分区函数不存在' end