SQL Server FileStream (转载)
从SQL SERVER 2008开始,SQL SERVER引入了一种新的文件组类型叫FileStream文件组,如下图所示:
那么这种文件组是用来做什么的呢?
以往我们对文件管理有两种方法:
- 数据库只保存文件的路径,具体的文件保存在文件服务器(NFS)上,使用时,编程实现从文件服务器读取文件;
- 将文件直接以varbinary(max)或image数据类型保存在数据库中。
上面两种文件存放方式都有问题:第一种方法因为会访问磁盘,故受I/O影响性能不是很好,而且不能很好的进行文件备份;第二种方法虽然解决了文件备份(数据库的备份)问题,但是由于字段的字节数太大,对数据库本身也会造成影响,性能也很低下。
微软在SQL Server 2008推出了一种新的方式 - FileStream,它不是一种新的数据类型,而是一种技术,它使SQL Server数据库引擎和NTFS文件系统成为了一个整体,它结合了上面两种方式的优点:FileStream使用NT系统来缓存文件数据,而对文件数据的操作可使用Transact-SQL语句对其进行插入、更新、查询、搜索和备份。
https://msdn.microsoft.com/en-us/library/gg471497(v=sql.110).aspx
FILESTREAM integrates the SQL Server Database Engine with an NTFS file system by storing varbinary(max) binary large object (BLOB) data as files on the file system. Transact-SQL statements can insert, update, query, search, and back up FILESTREAM data. Win32 file system interfaces provide streaming access to the data.
FILESTREAM uses the NT system cache for caching file data. This helps reduce any effect that FILESTREAM data might have on Database Engine performance. The SQL Server buffer pool is not used; therefore, this memory is available for query processing.
也就是说FileStream文件组是用来代替数据库中的varbinary(max)或image类型,用来存储文件数据的,它可以随着数据库备份和还原,也支持绑定数据库的事务,完美解决了在SQL SERVER数据库中存储二进制文件的难题。
一、FileStream配置
1. 配置SQL Server安装实例:Start -> All Programs -> Microsoft SQL Server 2008 R2 -> Configuration Tools -> SQL Server Configuration Manager
右击属性,切换到FILESTREAM标签,勾选如下配置
2. 打开SQL Server,并配置如下
以上也可以通过如下脚本执行:
Exec sp_configure filesteam_access_level, 2
RECONFIGURE
最后重启SQL Server Service
二、实例展示
创建FileStream类型文件/组
FILESTREAM data must be stored in FILESTREAM filegroups. A FILESTREAM filegroup is a special filegroup that contains file system directories instead of the files themselves. These file system directories are called data containers. Data containers are the interface between Database Engine storage and file system storage.
--Create filestreamgroup
ALTER DATABASE [Archive]
ADD FILEGROUP [FileStreamGroup] CONTAINS FILESTREAM
GO --Create filestream and association with filestreamgroup above
ALTER DATABASE [Archive]
ADD FILE ( NAME = N'FileStream', FILENAME = N'D:\Company\Data\SQL Server\FileStream') TO FILEGROUP [FileStreamGroup]
GO
注意上面将FILEGROUP类型的文件组FileStreamGroup关联到FILENAME时,我们给FILENAME声明的实际上是一个文件夹:FILENAME = N'D:\Company\Data\SQL Server\FileStream',而不是个具体的文件,该文件夹会存储FileStreamGroup文件组中所有的相关文件,如下图所示:
filestream.hdr 文件是 FILESTREAM 容器的头文件。filestream.hdr 文件是重要的系统文件。它包含 FILESTREAM 标头信息。请勿删除或修改此文件。
创建测试表(注意:如果表包含FILESTREAM列,则每一行都必须具有唯一的行ID)
--Create table
CREATE TABLE Archive.dbo.Attachment (
[ID] [UNIQUEIDENTIFIER] ROWGUIDCOL NOT NULL PRIMARY KEY,
[FileName] NVARCHAR(100) NULL,
[CreateUser] NVARCHAR(100) NULL,
[CreateDatetime] DATETIME NULL,
[Content] VARBINARY(MAX) FILESTREAM NULL
)
FILESTREAM_ON [FileStreamGroup]
什么是rowguidcol关键字
rowguidcol:指定列为全球惟一鉴别行号列(rowguidcol是Row Global UniqueIdentifier Column的缩写)。此列的数据类型必须为UNIQUEIDENTIFIER类型。一个表中数据类型为UNIQUEIDENTIFIER的列中只能有一个列被定义为rowguidcol列。rowguidcol属性不会使列值具有惟一性,也不会自动生成一个新的数据值给插入行。需要在INSERT语句中使用NEWID()函数或指定列的默认值为NEWID()函数。
插入一些测试数据
--Insert some records
INSERT INTO Attachment VALUES
(NEWID(),'File Name 1','shg.cpan', GETDATE(),NULL),
(NEWID(),'File Name 1','shg.cpan', GETDATE(),CAST('' AS VARBINARY(MAX))),
(NEWID(),'File Name 1','shg.cpan', GETDATE(),CAST('This is a attachment, which contains all introduction for filestream' AS VARBINARY(MAX)))
从前台插入一些数据
using (SqlConnection conn = new SqlConnection("server=10.7.15.172;database=Archive;uid=sa;pwd=1234;Connect Timeout=180"))
{
conn.Open();
using (SqlCommand cmd = conn.CreateCommand())
{
string id = Guid.NewGuid().ToString();
cmd.CommandText = "INSERT INTO Attachment VALUES('" + id + "','File Name 2','shg.cpan','" + DateTime.Now + "',@content)";
SqlParameter param = new SqlParameter("@content", SqlDbType.VarBinary, );
param.Value = File.ReadAllBytes(@"D:\Folder\131 u_ex151207.log");
cmd.Parameters.Add(param);
cmd.ExecuteNonQuery();
}
conn.Close();
}
检索数据
SELECT DATALENGTH(CONTENT)/(1024.0 * 1024.0) AS MB,* FROM ATTACHMENT
结果
文件系统
上面的文件都是上传的真实文件,只不过没有后缀,如果重命名加上后缀,即可读取,如最后一个是excel文件,加上.xls,即可用Excel软件打开此文件。
下面我们再插入一条记录
INSERT INTO Attachment VALUES
(NEWID(),'Win32API','shg.cpan', GETDATE(),CAST('This is a attachment, which contains all introduction for filestream' AS VARBINARY(MAX)))
文件名00000016-0000016e-000c是如何和数据关联的呢
DBCC IND (Archive, Attachment, -1)
我们看一下PagePID为110的页情况(PageType为1表明是数据页)
DBCC TRACEON (3604);
DBCC PAGE (Archive, 1, 110, 3);
GO
看到了什么?CreateLSN即是我们在文件系统中看到的文件名00000016:0000016e:000c,这样数据库中的纪录就和文件联系起来了
三、使用 Win32 管理 FILESTREAM 数据
https://technet.microsoft.com/zh-cn/library/cc645940(v=sql.105).aspx
可以使用 Win32 在 FILESTREAM BLOB 中读取和写入数据。您需要执行以下步骤:
- 读取 FILESTREAM 文件路径;
- 读取当前事务上下文;
- 获取 Win32 句柄,并使用该句柄在 FILESTREAM BLOB 中读取和写入数据
读取 FILESTREAM 文件路径
DECLARE @filePath varchar(max) SELECT @filePath = Content.PathName()
FROM Archive.dbo.Attachment
WHERE FileName = 'Win32API'
PRINT @filepath
\\CHUNTING-PC\MSSQLSERVER\v1\Archive\dbo\Attachment\%!Content\583FFDB4-921B-4340-8247-130174488DC8
读取当前事务上下文
DECLARE @txContext varbinary(max) BEGIN TRANSACTION
SELECT @txContext = GET_FILESTREAM_TRANSACTION_CONTEXT()
PRINT @txContext
COMMIT
0x9D84E776FD943D419C99727C7AAA5B00
获取 Win32 句柄,并使用该句柄在 FILESTREAM BLOB 中读取和写入数据
若要获取 Win32 文件句柄,请调用 OpenSqlFilestream API。此 API 是从 sqlncli.dll 文件中导出的。可以将返回的句柄传递给以下任何 Win32 API:ReadFile、WriteFile、TransmitFile、SetFilePointer、SetEndOfFile 或 FlushFileBuffers。下面的示例说明了如何获取 Win32 文件句柄并使用它在 FILESTREAM BLOB 中读取和写入数据。
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.IO;
using System.Linq;
using System.Security.AccessControl;
using System.Text; namespace FileStreamConsoleApp
{
class Program
{
static void Main(string[] args)
{
SqlConnection sqlConnection = new SqlConnection("server=10.7.15.172;database=Archive;Connect Timeout=180;Integrated Security=true"); SqlCommand sqlCommand = new SqlCommand();
sqlCommand.Connection = sqlConnection; try
{
sqlConnection.Open(); //The first task is to retrieve the file path of the SQL FILESTREAM BLOB that we want to access in the application.
sqlCommand.CommandText = "SELECT Content.PathName() FROM Archive.dbo.Attachment WHERE FileName = 'Win32API'"; String filePath = null; Object pathObj = sqlCommand.ExecuteScalar();
if (DBNull.Value != pathObj)
{
filePath = (string)pathObj;
}
else
{
throw new System.Exception("Chart.PathName() failed to read the path name for the Chart column.");
} //The next task is to obtain a transaction context. All FILESTREAM BLOB operations occur within a transaction context to maintain data consistency. //All SQL FILESTREAM BLOB access must occur in a transaction. MARS-enabled connections have specific rules for batch scoped transactions,
//which the Transact-SQL BEGIN TRANSACTION statement violates. To avoid this issue, client applications should use appropriate API facilities for transaction management,
//management, such as the SqlTransaction class. SqlTransaction transaction = sqlConnection.BeginTransaction("mainTranaction");
sqlCommand.Transaction = transaction; sqlCommand.CommandText = "SELECT GET_FILESTREAM_TRANSACTION_CONTEXT()";//注意这里返回的就是事务上下文,该事务就是上面sqlConnection创建的事务transaction Object obj = sqlCommand.ExecuteScalar();
byte[] txContext = (byte[])obj;//事务上下文是数据库二进制类型 //The next step is to obtain a handle that can be passed to the Win32 FILE APIs.
SqlFileStream sqlFileStream = new SqlFileStream(filePath, txContext, FileAccess.ReadWrite);//这里要将事务上下文也传给SqlFileStream,这样该SqlFileStream就和前面创建的数据库事务transaction绑定了 byte[] buffer = new byte[]; int numBytes = ; //Write the string, "EKG data." to the FILESTREAM BLOB. In your application this string would be replaced with the binary data that you want to write. string someData = "EKG data.";
Encoding unicode = Encoding.GetEncoding(); sqlFileStream.Write(unicode.GetBytes(someData.ToCharArray()), , someData.Length); //Read the data from the FILESTREAM BLOB. sqlFileStream.Seek(0L, SeekOrigin.Begin); numBytes = sqlFileStream.Read(buffer, , buffer.Length); string readData = unicode.GetString(buffer); if (numBytes != )
{
Console.WriteLine(readData);
}
//Because reading and writing are finished, FILESTREAM must be closed. This closes the c# FileStream class,
//but does not necessarily close the the underlying FILESTREAM handle.
sqlFileStream.Close(); //The final step is to commit or roll back the read and write operations that were performed on the FILESTREAM BLOB. sqlCommand.Transaction.Commit();
}
catch (System.Exception ex)
{
Console.WriteLine(ex.ToString());
}
finally
{
sqlConnection.Close();
}
Console.ReadKey();
}
}
}
注意此处的连接方式,必须是integrated security,如果设置成如下
SqlConnection conn = new SqlConnection("server=10.7.15.172;database=Archive;uid=sa;pwd=1234;Connect Timeout=180")
会报错提示权限问题
这是因为实际的文件保存在服务器的硬盘上,读取时读到的文件是"\\xxxx",还是通过网上邻居,所以要使用信任连接
Only the account under which the SQL Server service account runs is granted NTFS permissions to the FILESTREAM container. We recommend that no other account be granted permissions on the data container.
Note |
---|
SQL logins will not work with FILESTREAM containers. Only NTFS authentication will work with FILESTREAM containers. |
另一篇文章 http://stackoverflow.com/questions/1398404/sql-server-filestream-access-denied
插入Attachment的完整C#语句
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.IO;
using System.Transactions; namespace FileStreamConsoleApp
{
public class ArchiveAttachment
{
private const string ConnStr = "server=10.7.15.172;database=Archive;Connect Timeout=180;Integrated Security=true"; public static void InsertAttachment(string Id, string fileName, string fileFullName, string createUser, DateTime createDatetime)
{
string InsertTSql = @"INSERT INTO Attachment(Id, FileName, CreateUser, CreateDatetime, Content) VALUES(@Id, @FileName, @CreateUser, @CreateDatetime, 0x);
SELECT Content.PathName(), GET_FILESTREAM_TRANSACTION_CONTEXT() FROM Attachment WHERE Id = @Id"; string serverPath;
byte[] serverTransaction; using (TransactionScope ts = new TransactionScope())
{
using (SqlConnection conn = new SqlConnection(ConnStr))
{
conn.Open(); using (SqlCommand cmd = new SqlCommand(InsertTSql, conn))
{
cmd.Parameters.Add("@Id", SqlDbType.NVarChar).Value = Id;
cmd.Parameters.Add("@FileName", SqlDbType.NVarChar).Value = fileName;
cmd.Parameters.Add("@CreateUser", SqlDbType.NVarChar).Value = createUser;
cmd.Parameters.Add("@createDatetime", SqlDbType.DateTime).Value = createDatetime;
using (SqlDataReader sdr = cmd.ExecuteReader())
{
sdr.Read();
serverPath = sdr.GetSqlString().Value;
serverTransaction = sdr.GetSqlBinary().Value;
sdr.Close();
}
}
SaveAttachment(fileFullName, serverPath, serverTransaction);
}
ts.Complete();
}
} private static void SaveAttachment(string clientPath, string serverPath, byte[] serverTransaction)
{
const int BlockSize = * ; using (FileStream source = new FileStream(clientPath, FileMode.Open, FileAccess.Read))
{
using (SqlFileStream dest = new SqlFileStream(serverPath, serverTransaction, FileAccess.Write))
{
byte[] buffer = new byte[BlockSize];
int bytesRead;
while ((bytesRead = source.Read(buffer, , buffer.Length)) > )
{
dest.Write(buffer, , bytesRead);
dest.Flush();
}
dest.Close();
}
source.Close();
}
}
}
}
四、使用场合
请注意以下事项:
并不是所有的文件存储都适合使用FileStream,如果所存储的文件对象平均大于1MB考虑使用FileStream,否则对于较小的文件对象,以varbinary(max)BLOB存储在数据库中通常会提供更为优异的流性能
https://msdn.microsoft.com/en-us/library/gg471497(v=sql.110).aspx
http://stackoverflow.com/questions/13420305/storing-files-in-sql-server#
http://research.microsoft.com/apps/pubs/default.aspx?id=64525
一些参考链接:
使用FILESTREAM最佳实践:https://technet.microsoft.com/zh-cn/library/dd206979(v=sql.105).aspx
设计和实现 FILESTREAM 存储:https://technet.microsoft.com/zh-cn/library/bb895234%28v=sql.105%29.aspx?f=255&MSPPError=-2147217396
二进制大型对象 (Blob) 数据 (SQL Server):https://technet.microsoft.com/zh-cn/library/bb895234(v=sql.110).aspx
Files and Filegroups Architecture:https://msdn.microsoft.com/en-us/library/ms179316.aspx?f=255&MSPPError=-2147217396
FILESTREAM Storage in SQL Server 2008:https://msdn.microsoft.com/en-us/library/hh461480.aspx?f=255&MSPPError=-2147217396
SQL Server FileStream (转载)的更多相关文章
- SQL Server FileStream
以往我们对文件管理有两种方法: 数据库只保存文件的路径,具体的文件保存在文件服务器(NFS)上,使用时,编程实现从文件服务器读取文件: 将文件直接以varbinary(max)或image数据类型保存 ...
- SQL Server 存储过程(转载)
SQL Server 存储过程 Transact-SQL中的存储过程,非常类似于Java语言中的方法,它可以重复调用.当存储过程执行一次后,可以将语句缓存中,这样下次执行的时候直接使用缓存中的语句.这 ...
- SQL Server FileStream优点与不足
LOB优点: 1.保证大对象的事务一致性. 2.备份与还原包括大数据对象,可以对它进行时点恢复. 3.所有数据都可以使用一种存储与查询环境. LOB不足: 1.大型对象在缓存中占非常大的缓存区. 2. ...
- SQL Server Profiler(转载)
SQL Server Profiler工具 一.SQL Profiler工具简介 SQL Profiler是一个图形界面和一组系统存储过程,其作用如下: 图形化监视SQL Server查询: 在后台收 ...
- SQL Server 2008 FILESTREAM特性管理文件
在SQL Server 2008中,新的FILESTREAM(文件流)特性和varbinary列配合,你可以在服务器的文件系统上存储真实的数据,但可以在数据库上下文内管理和访问,这个特性让SQL Se ...
- SQL Server 内存中OLTP内部机制概述(三)
----------------------------我是分割线------------------------------- 本文翻译自微软白皮书<SQL Server In-Memory ...
- SQL Server游标 C# DataTable.Select() 筛选数据 什么是SQL游标? SQL Server数据类型转换方法 LinQ是什么? SQL Server 分页方法汇总
SQL Server游标 转载自:http://www.cnblogs.com/knowledgesea/p/3699851.html. 什么是游标 结果集,结果集就是select查询之后返回的所 ...
- 使用Source Safe for SQL Server解决数据库版本管理问题(转载)
简介 在软件开发过程中,版本控制是一个广为人知的概念.因为一个项目可能会需要不同角色人员的参与,通过使用版本控制软件,可以使得项目中不同角色的人并行参与到项目当中.源代码控制使得代码可以存在多个版本, ...
- sql server 本地复制订阅 实现数据库服务器 读写分离(转载)
转载地址:http://www.cnblogs.com/echosong/p/3603270.html 再前段echosong 写了一遍关于mysql 数据同步实现业务读写分离的文章,今天咱们来看下S ...
随机推荐
- 在自己网站中引入CU3ER/Flash 3D幻灯片效果和照片画框
要求 必备知识 本文要求基本了解 JAVASCRIPT 和 XML基础语法知识. 运行环境 支持Flash Player的浏览器/Flash Player11及以上 演示地址 演示地址 下载地址 ...
- nginx反向代理如何获取真实IP?
由于客户端和web服务器之间增加了中间层,因此web服务器无法直接拿到客户端的ip,通过$remote_addr变量拿到的将是反向代理服务器的ip地址. 1.安装--with-http_realip_ ...
- springboot-30-security(三)使用注解实现权限控制
上个博客: http://www.cnblogs.com/wenbronk/p/7381252.html中, 实现了经典5表对用户进行权限的控制, 但太过于繁琐了, 官方推荐的方式是将用户和角色存储数 ...
- 【K8S学习笔记】Part2:获取K8S集群中运行的所有容器镜像
本文将介绍如何使用kubectl列举K8S集群中运行的Pod内的容器镜像. 注意:本文针对K8S的版本号为v1.9,其他版本可能会有少许不同. 0x00 准备工作 需要有一个K8S集群,并且配置好了k ...
- ThreadLocal剧集(一)
总述 最近做了一个日志调用链路跟踪的项目,涉及到操作标识在线程和子线程,线程池以及远程调用之间的传递问题.最终采用了阿里开源的TransmittableThreadLocal插件(https: ...
- POJ 2419 Forests(模拟)
题目链接: https://cn.vjudge.net/problem/POJ-2419 题目描述: If a tree falls in the forest, and there's nobody ...
- el-upload源码跳坑2
产品又加了一个需求,要求删除图片时候弹一个提示框,如果确定就直接发请求从服务器删除图片 一开始想的比较简单,直接在on-remove的钩子函数上做弹框提示,如果取消就撤销,代码如下: <el ...
- ASP.NET开发,从二层至三层,至面向对象 (4)
继续上一篇<ASP.NET开发,从二层至三层,至面向对象 (3)>http://www.cnblogs.com/insus/p/3826706.html .我们更深层次学会了逻辑层对象,即 ...
- access数据库 配置路径
<configuration> <system.web> <compilation debug="true" targetFramework=&quo ...
- Eclipse中Maven WEB工程tomcat调试
最近没事了玩一下maven,使用maven管理工程中的依赖包非常的方便.建立maven web工程的时候开始不知道怎么用tomcat来调试,总是使用mave的tomcat插件发布了后来调试,觉得非常的 ...