我正在寻找一种解决方案来替换分层结构化数据的实际保存算法,因为我们遇到了重大的性能问题。
我们的分层结构化数据包含2个表格(observation
,value
)。以下是每个实体的规范:
observation
和value
表的剥离T-SQL定义:
CREATE TABLE Observation(
Id int IDENTITY(1,1) NOT NULL,
DocumentParentId int NULL,
ObsParentId int NULL,
ValueParentId int NULL,
EditionDate Datetime NOT NULL,
State tinyint NOT NULL,
Comment varchar(max) NULL,
CONSTRAINT [PK_Observation] PRIMARY KEY (Id)
)
CREATE TABLE Value(
Id int IDENTITY(1,1) NOT NULL,
ObservationId int NOT NULL,
Data varchar(max) NULL,
CONSTRAINT PK_Value PRIMARY KEY (Id)
)
当前保存算法(简化)
通过第一级观察迭代
foreach (Observation obs in doc.Roots)
{
obs.SaveObservation(doc.Id, null, null, connection)
}
保存observation
实体的逻辑:
public void SaveObservation(int? docParentId, int? obsParentId, int? valParentId, SqlConnection conn, SqlTransaction trans)
{
string sql =
@"INSERT INTO Observation (DocumentParentId, ObsParentId, ValueParentId, EditionDate, State, Comment)
VALUES (@docId, @oparentId, @vparentId, @date, @state, @comment)
SELECT scope_identity())";
SqlCommand cmd = new SqlCommand(sql, conn, trans);
if (docParentId.HasValue)
cmd.Parameters.Add("@docId", SqlDbType.Int).Value = docParentId.Value;
else
cmd.Parameters.Add("@docId", SqlDbType.Int).Value = DBNull.Value;
if (obsParentId.HasValue)
cmd.Parameters.Add("@oparentId", SqlDbType.Int).Value = obsParentId.Value;
else
cmd.Parameters.Add("@oparentId", SqlDbType.Int).Value = DBNull.Value;
if (valParentId.HasValue)
cmd.Parameters.Add("@vparentId", SqlDbType.Int).Value = valParentId.Value;
else
cmd.Parameters.Add("@vparentId", SqlDbType.Int).Value = DBNull.Value;
//... insert internal properties
this.Id = (int)cmd.ExecuteScalar(); //insert and obtains id
foreach (Observation child in this.Children)
this.SaveObservation(null, this.Id, null, conn, trans);
foreach (Value val in this.Values)
val.SaveValue(this.Id, conn, trans);
}
保存value
实体的逻辑:
public void SaveValue(int parentobs, SqlConnection conn, SqlTransaction trans)
{
//save value with an insert sql command
//...
foreach(var obs in this.Dependents)
obs.SaveObservation(null, null, this.Id, conn, trans);
}
在当前的算法中,它们是数据库中要插入的每个observation
和value
实体的一个请求。
通过测试,10 000个观察值,保存文档需要2秒(在测试环境中,仅限一个用户)。
我尝试更改当前算法,以便服务器只发送一个完整保存逻辑请求。
首次测试(stringbuilder)
我的第一个想法是使用字符串构建器将所有insert命令写入一个字符串中,然后在SQL上执行该字符串。 为了观察我写的
DECLARE @obs int
insert ...
Set @obs = SELECT scope_identity()
etc.
对于我的测试文档,逻辑创建了一个15 meg的字符串。 SQL在执行查询时失败,因为它有超过10 000个声明的变量。
第二次测试(存储过程和表格参数)
观察的临时表
CREATE TYPE SaveObservationType AS TABLE(
ID uniqueidentifier NOT NULL,
DocParentId int NULL,
ObsParentID uniqueidentifier NULL,
ValueParnetID uniqueidentifier NULL,
...
PRIMARY KEY CLUSTERED
(
[ID] ASC
)
值的暂存表
CREATE TYPE SaveObservationValueType AS TABLE(
ID uniqueidentifier NOT NULL,
ObservationID uniqueidentifier NOT NULL,
...
PRIMARY KEY CLUSTERED
(
ID ASC
)
)
在c#中,我浏览我的结构并插入guid作为临时ID并填充临时表。
在存储过程中,我有一个光标可以浏览第一级观察,一个用于观察孩子的光标,以及一个用于儿童观察值的光标。
对于大数据,这非常慢(比当前算法慢〜3倍),
但对于小数据,它需要相同的时间。我认为性能问题是由游标的使用引起的
我使用游标是因为我需要一次插入一个游标以获得它在我浏览他的孩子和值时使用的id。
第三次测试(批量复制和存储过程):
我在临时表SaveObservationValue
中批量复制我的数据。然后,我调用存储过程来导航此表中的记录以将数据插入数据库中。这与第二个测试类似,但是临时表具有索引。
表格的每条记录都包含观察数据或价值数据 在存储过程中,我使用游标来导航结构。
对于大数据(6000个观测值和值),它需要大约3秒(比我当前的算法慢50%)。在此测试中,性能的瓶颈是存储过程(2.5秒),可能是由于使用了游标。
摘要
我测试的3种方法都没有定论。在每种情况下,当前算法最快,但它仍然(在我看来)对网络的速度要求很慢(服务器和数据库都在2台不同的机器上)。