从数据库中检索数据的最快方法

enb*_*081 26 .net c# sql asp.net sql-server-2008

我正在使用C#和Sql Server 2008开发一个ASP.NET项目.

我有三张桌子:

用户 数据域 DataField值

每个用户对每个数据字段都有一个特定值,该值存储在DataFieldsValues中.

现在我想显示一个如下所示的报告:

在此输入图像描述

我创建了对象User,和DataField.在DataField对象中,有Method string GetValue(User user),我在其中获取某个用户的字段值.

然后我有用户List<User> users列表和DataFields列表,List<DataField> fields我执行以下操作:

string html = string.Empty;
html += "<table>";
html += "<tr><th>Username</th>";
foreach (DataField f in fields)
{
   html += "<th>" + f.Name + "</th>";
}
html += "</tr>"

foreach (User u in users)
{
   html += "<tr><td>" + u.Username + "</td>"
   foreach (DataField f in fields)
   {
      html += "<td>" + f.GetValue(u) + "</td>";
   }
   html += "</tr>"
}
Response.Write(html);
Run Code Online (Sandbox Code Playgroud)

这工作正常,但它非常慢,我说的是20个用户和10个数据字段.在性能方面有没有更好的方法来实现这一目标?

编辑:对于类中的每个参数,我使用以下方法检索值:

public static string GetDataFromDB(string query)
{
    string return_value = string.Empty;
    SqlConnection sql_conn;
    sql_conn = new SqlConnection(ConfigurationManager.ConnectionStrings["XXXX"].ToString());
    sql_conn.Open();
    SqlCommand com = new SqlCommand(query, sql_conn);
    //if (com.ExecuteScalar() != null)
    try
    {
        return_value = com.ExecuteScalar().ToString();
    }
    catch (Exception x)
    {
    }
    sql_conn.Close();
    return return_value;
} 
Run Code Online (Sandbox Code Playgroud)

例如:

public User(int _Id)
{
this.Id = _Id
this.Username = DBAccess.GetDataFromDB("select Username from Users where Id=" + this.Id)
 //...
}
Run Code Online (Sandbox Code Playgroud)

Chr*_*yva 22

以下是2条有用的建议.第一个建议是显着改善您的表现.第二个建议也有帮助,但可能不会让你的应用程序更快.

建议1

GetDataFromDB(string query)经常调用这个方法.这很糟糕,因为每次都会创建一个新的SqlConnection和SqlCommand.这需要时间和资源.此外,如果有任何网络延迟,则乘以您正在进行的呼叫数.所以这只是个坏主意.

我建议您调用该方法一次并让它填充一个类似的集合,Dictionary<int, string>以便您可以从用户标识键快速查找您的用户名值.

像这样:

// In the DataField class, have this code.
// This method will query the database for all usernames and user ids and
// return a Dictionary<int, string> where the key is the Id and the value is the 
// username. Make this a global variable within the DataField class.
Dictionary<int, string> usernameDict = GetDataFromDB("select id, username from Users");

// Then in the GetValue(int userId) method, do this:
public string GetValue(int userId)
{
    // Add some error handling and whatnot. 
    // And a better name for this method is GetUsername(int userId)
    return this.usernameDict[userId];
}
Run Code Online (Sandbox Code Playgroud)

建议2

这是另一种可以改进的方法,虽然在这种情况下略有改进 - 使用StringBuilder该类.性能显着提升(这里是概述:http://support.microsoft.com/kb/306822).

SringBuilder sb = new StringBuilder();
sb.Append("<table><tr><th>Username</th>");
foreach (DataField f in fields)
{
    sb.Append("<th>" + f.Name + "</th>");
}

// Then, when you need the string
string html = sb.ToString();
Run Code Online (Sandbox Code Playgroud)

如果您需要更多说明,请告诉我,但您要求的是非常可行的.我们可以解决这个问题!

如果您进行这两项简单的更改,您将获得出色的表现.我保证.

  • 我只打开了一个带数据库的SqlConnection并保存在会话中.这大大提高了性能.StringBuilder也改进了它.这个答案有两个建议,尽管它们也在其他答案中都有提到.非常感谢您的宝贵意见! (2认同)

Rem*_*anu 18

您选择的数据库设计名为Entity-Attribute-Value,这是一种以其性能问题而闻名的设计.SQL Server团队发布了一份关于EAV设计指南的白皮书,请参阅针对性能和可伸缩性的语义数据建模的最佳实践.

唉,你已经有了设计,你现在可以做些什么呢?重要的是将对dB的miriad调用减少到单个调用,并执行一个面向集合的语句来检索数据.游戏名称是表值参数:

declare @users as UsersType;

insert into @users (UserId) values (7), (42), (89);

select ut.Id, 
  ut.Username, 
  df.Name as DataFieldName, 
  dfv.Value
from Users ut
join @users up on ut.Id = up.UserId
join DataFieldValues dfv on ut.Id = dfv.UserId
join DataFields df on dfv.DataFieldId = df.Id
order by ut.Id;
Run Code Online (Sandbox Code Playgroud)

有关完整示例,请参阅此SqlFiddle.

虽然严格来说,可以使用PIVOT运算符检索所需形状的结果(数据字段名称转换为列名称),但我强烈建议不要这样做.PIVOT本身就是一个性能泥潭,但是当你添加所需结果集的动态性质时,基本上不可能将它拉下来.由一行每个属性组成的传统结果集很容易解析为表,因为用户标识所需的顺序保证了相关属性集之间的干净中断.


Kas*_*ols 14

这很慢,因为在引擎盖下,您对数据库进行了20 x 10 = 200次查询.正确的方法是一次加载所有东西.

您应该发布有关加载数据方式的一些详细信息.如果您使用的是Entity Framework,则应使用Include命令使用名为Eager Loading的内容.

// Load all blogs and related posts
var blogs1 = context.Blogs
                      .Include(b => b.Posts)
                      .ToList();
Run Code Online (Sandbox Code Playgroud)

一些示例可以在这里找到:http://msdn.microsoft.com/en-us/data/jj574232.aspx

编辑:

您似乎没有使用.NET Framework提供的工具.如今,您不必为自己喜欢的简单风格做自己的数据库访问.此外,您应该像以前一样避免连接字符串HTML.

我建议你使用现有的ASP.NET控件和Entity Framework重新设计你的应用程序.

以下是为您提供分步说明的示例:http: //www.codeproject.com/Articles/363040/An-Introduction-to-Entity-Framework-for-Absolute-B

  • 我说如果你按照它的使用方式使用.NET,你就不会遇到这个问题.在您的情况下优化性能的最佳方法是减少数据库调用.一种方法是开始使用实体框架,这将简化数据访问. (5认同)

Mac*_*ack 6

正如Remus Rusanu所说,你可以使用PIVOT关系运算符以你需要的格式获得你想要的数据,就PIVOT的性能而言,我发现它将取决于你的表的索引和变化和数据集的大小.我非常有兴趣听取他对PIVOTs的看法,因为我们都在这里学习.有PIVOT上的大讨论VS JOINS 这里.

如果DataFields表是静态集,那么您可能不需要担心动态生成SQL,并且可以自己构建存储过程; 如果确实有所不同,您可能需要考虑动态SQL的性能损失(这是一篇很好的文章)或使用不同的方法.

除非您进一步需要数据,否则尝试将返回的集合保持在显示所需的最小值,这是减少开销的好方法,因为除非您的数据库与Web服务器位于同一物理服务器上,否则所有内容都需要通过网络.

确保尽可能少地执行单独的数据调用,这将减少用于提升和断开连接的时间.

当循环控件基于(可能是相关的?)数据集时,你应该总是仔细检查循环内的数据调用,因为这会尖叫JOIN.

当您尝试使用SQL时,尝试熟悉执行计划,这些将帮助您找出运行速度慢的原因查看这些资源以获取更多信息.

无论你接近什么,你决定你需要弄清楚你的代码中的瓶颈在哪里,基本的步骤可以帮助你解决这个问题,因为它可以帮助你自己查看问题所在,这也可以让你识别为你自己可能的方法问题,并建立良好的设计选择习惯.

马克碎石有一些有趣的点,做出关于C#数据读取这里的文章是有点老,但值得一读.

PIVOTing您的数据.(抱歉Remus ;-)) 基于您提供的数据示例,以下代码将获得您所需的内容,而不会进行查询内递归:

--Test Data
DECLARE @Users AS TABLE ( Id int
                        , Username VARCHAR(50)
                        , Name VARCHAR(50)
                        , Email VARCHAR(50)
                        , [Role] INT --Avoid reserved words for column names.
                        , Active INT --If this is only ever going to be 0 or 1 it should be a bit.
                        );

DECLARE @DataFields AS TABLE ( Id int
                        , Name VARCHAR(50)
                        , [Type] INT --Avoid reserved words for column names.
                        );

DECLARE @DataFieldsValues AS TABLE ( Id int
                        , UserId int
                        , DataFieldId int
                        , Value VARCHAR(50)
                        );

INSERT INTO @users  ( Id
                    , Username
                    , Name
                    , Email
                    , [Role]
                    , Active) 
VALUES (1,'enb081','enb081','enb081@mack.com',2,1),
       (2,'Mack','Mack','mack@mack.com',1,1),
       (3,'Bob','Bobby','bob@mack.com',1,0)


INSERT INTO @DataFields  
                    ( Id
                    , Name
                    , [Type]) 
VALUES (1,'DataField1',3),
       (2,'DataField2',1),
       (3,'DataField3',2),
       (4,'DataField4',0)

INSERT INTO @DataFieldsValues  
                    ( Id
                    , UserId
                    , DataFieldId
                    , Value) 
VALUES (1,1,1,'value11'),
       (2,1,2,'value12'),
       (3,1,3,'value13'),
       (4,1,4,'value14'),
       (5,2,1,'value21'),
       (6,2,2,'value22'),
       (7,2,3,'value23'),
       (8,2,4,'value24')

--Query
SELECT *
FROM
(   SELECT  ut.Username, 
            df.Name as DataFieldName, 
            dfv.Value
    FROM @Users ut
    INNER JOIN @DataFieldsValues dfv 
        ON ut.Id = dfv.UserId
    INNER JOIN @DataFields df 
        ON dfv.DataFieldId = df.Id) src
PIVOT
(   MIN(Value) FOR DataFieldName IN (DataField1, DataField2, DataField3, DataField4)) pvt

--Results
Username    DataField1  DataField2  DataField3  DataField4
enb081      value11     value12     value13     value14
Mack        value21     value22     value23     value24
Run Code Online (Sandbox Code Playgroud)

要记住的最重要的事情是为自己尝试一些事情,因为我们建议的任何内容可能会被您所不知道的网站因素所改变.


ant*_*tao 5

你是如何访问数据库的?例如,如果您使用EF,请使用Profiler从这些查询中检查生成的SQL.不要每次都在foreach循环中建立连接.

我也不会在服务器端构建html.只需返回页面数据源控件的对象.


evi*_*lom 5

确保您没有为每个循环建立数据库连接.

我可以看到,f.GetValue(u)部分是一个返回从数据库中获取的字符串值的方法.

将数据一次性放入一个对象中并执行与f.GetValue(u)在此处所做的相同的操作.