我有一个包含多个文件的文件夹,我打算读取这些文件并将其插入类型指定的表中。
问题:
我正在读取具有多个线程的文件,但是每次一个线程尝试读取另一个线程正在读取的文件的类型(字符串)时,它都会崩溃并显示错误:
事务(进程ID 69)在锁定时死锁|通讯 用另一个进程缓冲资源,并已被选为 死锁的受害者。重新运行事务。在 System.Data.SqlClient.SqlConnection.OnError(SqlException异常, 布尔值breakConnection,动作
1 wrapCloseInAction) at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose) at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady) at System.Data.SqlClient.SqlCommand.RunExecuteNonQueryTds(String methodName, Boolean async, Int32 timeout, Boolean asyncWrite) at System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource
1 完成,字符串methodName,布尔型sendToPipe,Int32超时, 布尔值和usedCache,布尔值asyncWrite,布尔值inRetry) System.Data.SqlClient.SqlCommand.ExecuteNonQuery()在 Microsoft.Practices.EnterpriseLibrary.Data.Database.DoExecuteNonQuery(DbCommand 命令) Microsoft.Practices.EnterpriseLibrary.Data.Database.ExecuteNonQuery(DbCommand 命令)
我可以用一个线程来完成,但是要花很多时间,使用多线程,如何避免崩溃?
代码:
public int InsertData(string data, string tableName)
{
string query;
var token = JToken.Parse(data)["data"];
JArray jArrayData;
if (token is JArray)
{
jArrayData = token as JArray;// JArray.Parse(data) as JArray;
foreach (var item in jArrayData.Children())
{
var itemProperties = item.Children<JProperty>();
query = ServerHelper.CreateInsertQuery(itemProperties, tableName);
ExecuteQuery(query);
}
}
return 0;
}
private int ExecuteQuery(string query)
{
if(conn.State == ConnectionState.Closed)
{
conn.Open();
}
SqlCommand cmd = new SqlCommand(query, conn);
return cmd.ExecuteNonQuery();
}
public static string CreateInsertQuery(JEnumerable<JProperty> itemProperties, string tableName)
{
string[] nameArray = itemProperties.Select(x => x.Name).ToArray();
string[] valuesArray = itemProperties.Select(x => "'" + x.Value.ToString().Replace("'","''") + "'").ToArray();
//check for null
string query = "Insert Into " + tableName + " (" + string.Join(",", nameArray) + @")"
+ " values(" + string.Join(",", valuesArray) + ")";
return query;
}
尝试并行执行查询的代码是:
public void PerformInitialization()
{
ts = new CancellationTokenSource();
ct = ts.Token;
ChangeProcToDat();
string strMaxThreads = ConfigurationManager.AppSettings["MaxThreads"].ToString();
if(!string.IsNullOrEmpty(strMaxThreads))
{
bool parseSuccess = Int32.TryParse(strMaxThreads, out maxThreads);
if(parseSuccess == false)
{
maxThreads = 1;
}
}
processQueueTasks = new Task[maxThreads];
processData = new ProcessData();
for (int i = 0; i < maxThreads; i++)
{
processQueueTasks[i] = Task.Factory.StartNew(() => ProcessQueue(), ct);
}
}
public void ProcessQueue()
{
bool cancelled = false;
try
{
while (!ct.IsCancellationRequested)
{
try
{
Directory.CreateDirectory(queueFolderPath);
DirectoryInfo queueFolderDi = new DirectoryInfo(queueFolderPath);
FileInfo datFile = queueFolderDi.GetFiles("*.dat", SearchOption.AllDirectories).OrderBy(f => f.LastWriteTime).ToList().FirstOrDefault();
while (datFile != null)
{
string processingFileName = datFile.FullName.Replace(".dat", ".proc");
FileInfo file = new FileInfo(processingFileName);
File.Move(datFile.FullName, processingFileName);
try
{
if (file.Name.Contains("_ABC_"))
{
var fileNameArr = file.Name.Split('_', '.');
var fileType = ""; # this is the type of the file
if (fileNameArr.Length >= 3)
{
fileType = fileNameArr[3];
}
// the db part
var content = File.ReadAllText(file.FullName);
int responseCode = 0;
InsertData(content, reportType);
File.Delete(file.FullName);
}
}
catch (Exception ex)
{
Log.WriteToFailed("Error processing queue file " + file.Name + " "
+ Environment.NewLine + ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine);
}
datFile = queueFolderDi.GetFiles("*.dat", SearchOption.AllDirectories).OrderBy(f => f.LastWriteTime).ToList().FirstOrDefault();
}
}
catch (Exception ex)
{
Log.WriteToFailed("Error processing files in the queue folder."
+ Environment.NewLine + ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine);
}
finally
{
cancelled = ct.WaitHandle.WaitOne(1000);
}
}
}
catch (Exception ex)
{
Log.WriteToFailed("Error occured in send thread. Message:" + ex.Message + "\n Stack trace:" + ex.StackTrace);
}
}
答案 0 :(得分:0)
类似于 actual 问题,是如何快速将大量JSON文件中的记录插入SQL Server表中。
性能和僵局
尝试的解决方案很慢,因为它试图插入记录逐行行(RBAR)。这是最慢的方法。
它死锁是因为它使用的连接寿命长,这意味着只要连接保持打开状态,行和表锁就会被累积并保持。如果表没有主键或非聚集索引,则SQL Server可能必须在数据页甚至表级别上获取更多锁,并在连接保持打开状态时保持它们。
在这种情况下使用线程处理会使情况变得更糟,因为多个连接会互相等待。如果缺少索引导致过度锁定,则连接可能最终锁定彼此所需的数据页,从而导致死锁。
有许多种将JSON数据插入SQL Server的方法
SQL Server 2016及更高版本中的JSON 。
SQL Server 2016并具有可用于解析JSON字符串的JSON函数。可以编写一个存储过程,该过程使用JSON字符串作为参数,使用OPENJSON对其进行解析,然后将值插入表中。从OPENJSON – The easiest way to import JSON text into table窃取示例:
INSERT INTO Person
SELECT *
FROM OPENJSON(@json)
WITH (id int,
firstName nvarchar(50), lastName nvarchar(50),
isAlive bit, age int,
dateOfBirth datetime2, spouse nvarchar(50))
将文件的全部内容发送到SQL Server不会比发送单个文本值使用更多数据。
OPENROWSET
另一种选择是直接使用OPENROWSET加载JSON文件,然后使用OPENJSON对其进行解析,例如:
INSERT INTO TargetTable(ID,Name,Price,Pages,Author)
SELECT book.id, book.name, book.price, book.pages_i, book.author
FROM OPENROWSET (BULK 'C:\JSON\Books\books.json', SINGLE_CLOB) as j
CROSS APPLY OPENJSON(BulkColumn)
WITH( id nvarchar(100), name nvarchar(100), price float,
pages_i int, author nvarchar(100)) AS book
客户端解析和SqlBulkCopy
另一种选择是在客户端(例如,使用JSON.NET)解析文本,并使用SqlBulkCopy大容量插入数据。 SqlBulkCopy使用BULK INSERT
或bcp
使用的相同机制,以最少的日志记录尽可能快地插入数据。
FastMember's ObjectReader可用于将项目列表转换为SqlBulkCopy期望的IDataReader。代码可能是这样的:
var json=File.ReadAllText(path);
var data=JsonConvert.DeserializeObject<List<SomeType>>(json);
using(var bcp = new SqlBulkCopy(connection))
using(var reader = ObjectReader.Create(data, "Id", "Name", "Description"))
{
bcp.DestinationTableName = "SomeTable";
bcp.WriteToServer(reader);
}
或者,作为一种方法:
void ImportJson<T>(string path,string tableName,string[] fields)
{
var json=File.ReadAllText(path);
var data=JsonConvert.DeserializeObject<List<T>>(json);
using(var bcp = new SqlBulkCopy(connection))
using(var reader = ObjectReader.Create(data, fields))
{
bcp.DestinationTableName = tableName;
bcp.WriteToServer(reader);
}
}
客户端解析和多行INSERT
代替执行单个INSERT语句,可以通过批处理或使用多行INSERT语句来提高性能,例如:
INSERT INTO TABLE TableA (Col1,Col2)
VALUES
(Val11,Val12),
(Val21,Val22)
尽管手工书写很无聊。一个人可以使用马克·格雷夫(Marc Gravell)的另一作品Dapper来执行多个插入操作:
var json=File.ReadAllText(path);
var data=JsonConvert.DeserializeObject<List<T>>(json);
conn.Execute("INSERT INTO TableA (Col1,Col2) VALUES(@Prop1,@Prop2)",data);
还有其他选择。数据可以作为表值参数发送,另一件事是easier to do with Dapper