以往我们对文件管理有两种方法:

  1. 数据库只保存文件的路径,具体的文件保存在文件服务器(NFS)上,使用时,编程实现从文件服务器读取文件;
  2. 将文件直接以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配置

  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

  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]

  插入一些测试数据

--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 中读取和写入数据。您需要执行以下步骤:

  1. 读取 FILESTREAM 文件路径;
  2. 读取当前事务上下文;
  3. 获取 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:ReadFileWriteFileTransmitFileSetFilePointerSetEndOfFileFlushFileBuffers。下面的示例说明了如何获取 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()"; 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); 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 2008https://msdn.microsoft.com/en-us/library/hh461480.aspx?f=255&MSPPError=-2147217396

SQL Server FileStream的更多相关文章

  1. SQL Server FileStream (转载)

    从SQL SERVER 2008开始,SQL SERVER引入了一种新的文件组类型叫FileStream文件组,如下图所示: 那么这种文件组是用来做什么的呢? 以往我们对文件管理有两种方法: 数据库只 ...

  2. SQL Server FileStream优点与不足

    LOB优点: 1.保证大对象的事务一致性. 2.备份与还原包括大数据对象,可以对它进行时点恢复. 3.所有数据都可以使用一种存储与查询环境. LOB不足: 1.大型对象在缓存中占非常大的缓存区. 2. ...

  3. SQL Server 2008 FILESTREAM特性管理文件

    在SQL Server 2008中,新的FILESTREAM(文件流)特性和varbinary列配合,你可以在服务器的文件系统上存储真实的数据,但可以在数据库上下文内管理和访问,这个特性让SQL Se ...

  4. SQL Server 内存中OLTP内部机制概述(三)

    ----------------------------我是分割线------------------------------- 本文翻译自微软白皮书<SQL Server In-Memory ...

  5. sqlserver2008创建数据库 报 Cannot read property is filestream 此属性不可用于sql server 7.0 解决

    在创建数据库的时候,报整个错误 Cannot read property is filestream 此属性不可用于sql server 7.0 按照网上的方法  (http://blog.csdn. ...

  6. 在SQL Server 中启用 FileStream

    最近在研究在数据库中存储大数据文件,看到了FileStream 这个功能,记录下来以备后用 FileStream 一般在安装的时候默认是不启用的,如果你留意的话,在选择数据库文件路径那个窗口,有一个标 ...

  7. Sql Server系列:分区表操作

    1. 分区表简介 分区表在逻辑上是一个表,而物理上是多个表.从用户角度来看,分区表和普通表是一样的.使用分区表的主要目的是为改善大型表以及具有多个访问模式的表的可伸缩性和可管理性. 分区表是把数据按设 ...

  8. SQL Server 2014聚集列存储索引

    转发请注明引用和原文博客(http://www.cnblogs.com/wenBlog) 简介 之前已经写过两篇介绍列存储索引的文章,但是只有非聚集列存储索引,今天再来简单介绍一下聚集的列存储索引,也 ...

  9. SQL Server 2012 新特性:FileTable

    FileTable是基于FILESTREAM的一个特性.有以下一些功能: 一行表示一个文件或者目录. 每行包含以下信息: file_Stream流数据,stream_id标示符(GUID). 用户表示 ...

随机推荐

  1. C basics

    C 日记目录 C basics ................ writing Numeration storage   , structor space assigning pointer,  a ...

  2. 关于ES、PES、PS/TS 码流

    一.基本概念 )ES   ES--Elementary  Streams  (原始流)是直接从编码器出来的数据流,可以是编码过的视频数据流(H.264,MJPEG等),音频数据流(AAC),或其他编码 ...

  3. css部分的复习

    常见的块元素有<h1><h6>.<p><div><ul><li><ol>等,其中<div>标记是最典型的 ...

  4. storyboard xib下label怎么自适应宽度高度

    先看需求:两个Label,要求蓝色的label紧跟在红色的label文字后面  ok首选正常添加约束 红色的Label添加宽度,高度,左边,上边约束 蓝色的Label添加宽度,高度,左边,和红色的水平 ...

  5. getch 和 getchar 在 windows 和 unix下的区别

    注意getch()是从console读取,(非标准函数) getch()需要的头文件是<conio.h>. 而getchar()是从stdin,一般是指键盘 windows平台下ENTER ...

  6. 深刻理解和运用XMLHttpRequest

    本文为转载文章,因见猎心喜,担心失传,故贴此以备不时之需. 原文地址:传送 你真的会使用XMLHttpRequest吗? xmlhttprequest http cors ajax ruoyiqing ...

  7. bzoj 2152聪聪可可

    2152: 聪聪可可 Time Limit: 3 Sec  Memory Limit: 259 MB Description 聪聪和可可是兄弟俩,他们俩经常为了一些琐事打起来,例如家中只剩下最后一根冰 ...

  8. 模板(Template)

    最近阅读google chromium base container stack_container代码,深刻感觉到基础知识不扎实. // Casts the buffer in its right ...

  9. Daily Scrum 12.12

    今日完成任务: 解决文档上传时TagAssociation的建立异常问题:解决问题页面标签点击卡死的BUG. 发现问题: 文档下载量浏览量显示不正确: 文档打开时全都是同一个PDF: 右侧最佳资源的显 ...

  10. python学习之路-day1-python基础1

    本节内容: Python介绍 发展史 Python 2 or 3? 安装 Hello World程序 变量 用户输入 模块初识 .pyc是个什么鬼? 数据类型初识 数据运算 表达式if ...else ...