Подтвердить что ты не робот

Самый быстрый способ получить данные из базы данных

Я работаю над проектом ASP.NET с С# и Sql Server 2008.

У меня есть три таблицы:

UsersDataFieldsDataField Values

Каждый пользователь имеет определенное значение для каждого поля данных, и это значение сохраняется в DataFieldsValues.

Теперь я хочу отобразить отчет, который выглядит так:

enter image description here

Я создал объекты User и DataField. В объекте DataField существует метод string GetValue(User user), в котором я получаю значение поля для определенного пользователя.

Затем у меня есть список Users 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);

Это отлично работает, но чрезвычайно медленнее, и я говорю о 20 пользователях и 10 полях данных. Есть ли лучший способ с точки зрения производительности для достижения этого?

EDIT: для каждого параметра внутри классов я извлекаю значение, используя следующий метод:

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;
} 

Например:

public User(int _Id)
{
this.Id = _Id
this.Username = DBAccess.GetDataFromDB("select Username from Users where Id=" + this.Id)
 //...
}
4b9b3361

Ответ 1

Вот два предложения, которые помогут. Первое предложение - это то, что значительно улучшит вашу производительность. Второе предложение также поможет, хотя, вероятно, не сделает ваше приложение быстрее в вашем случае.

Предложение 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];
}

Предложение 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();

Сообщите мне, если вам нужно уточнить, но то, что вы просите, очень полезно. Мы можем это решить!

Если вы сделаете эти 2 простых изменения, вы получите отличную производительность. Я гарантирую это.

Ответ 2

Выбранный дизайн базы данных называется Entity-Attribute-Value, который хорошо известен своими проблемами производительности. Команда SQL Server выпустила технический документ для руководства по дизайну EAV, см. "Лучшие практики моделирования семантических данных для производительности и масштабируемости" .

Увы, у вас уже есть дизайн и что вы можете сделать с этим сейчас? Важно, чтобы уменьшить вызовы 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;

В качестве полного примера см. этот SqlFiddle.

Хотя, строго говоря, можно получить результат по желаемой форме (имена полей данных, перенесенные как имена столбцов) с помощью оператора PIVOT, Я бы очень категорически не советовал делать это. PIVOT сам по себе является трясиной производительности, но когда вы добавляете динамический характер желаемого набора результатов, в принципе невозможно его отключить. Традиционный набор результатов, состоящий из атрибута из одной строки, является тривиальным для синтаксического анализа в таблице, потому что требуемый порядок с помощью идентификатора пользователя гарантирует чистый разрыв между наборами коррелированных атрибутов.

Ответ 3

Это медленно, потому что под капотом вы делаете 20 x 10 = 200 запросов к базе данных. Правильный способ - загрузить все за один ход.

Вы должны опубликовать некоторые сведения о том, как вы загружаете данные. Если вы используете Entity Framework, вы должны использовать что-то под названием Eager Loading с помощью команды Include.

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

Некоторые примеры можно найти здесь: 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

Ответ 4

Как сказал Ремус Русану, вы можете получить нужные данные в нужном вам формате, используя реляционный оператор PIVOT, насколько это касается производительности PIVOT, я обнаружил, что это будет зависеть от индексации ваших таблиц и изменчивость и размер набора данных. Мне было бы очень интересно услышать от него больше о его мнении о PIVOT, поскольку мы все здесь, чтобы учиться. Здесь обсуждается PIVOT vs JOINS здесь.

Если таблица DataFields является статическим, тогда вам может не понадобиться динамически генерировать SQL, и вы можете создать себе хранимую процедуру; если он отличается, вам может потребоваться принять удар производительности динамического SQL (вот отличная статья об этом) или использовать другой подход.

Если вам больше не нужны данные, попробуйте сохранить возвращаемый набор до минимума, который вам нужен для отображения, это хороший способ уменьшить накладные расходы, так как все должно пройти по сети, если ваш db не находится на том же физическом сервере, что и веб-сервер.

Убедитесь, что вы выполняете как можно меньше отдельных вызовов данных, чтобы уменьшить время, затрачиваемое на повышение и удаление соединений.

Вы всегда должны дважды проверять вызовы данных в цикле, когда элемент управления для цикла основан на наборе данных (возможно, связанных?), так как это кричит JOIN.

Когда вы экспериментируете со своим SQL, попробуйте ознакомиться с планами выполнения, это поможет вам понять, почему вы выполняете медленные поисковые запросы. эти ресурсы для получения дополнительной информации.

Независимо от того, что вы приближаетесь к вам, вы решаете, что вам нужно выяснить, где узкие места находятся в вашем коде, что-то столь же простое, как переключение на выполнение, может помочь в этом, так как оно позволит вам самим убедиться, где проблемы лежат, это также позволит вы сами определяете возможные проблемы с вашим подходом и создаете хорошие привычки выбора дизайна.

У Марка Гравеля есть несколько интересных моментов, связанных с чтением данных С# здесь, эта статья немного устарела, но стоит прочитать.

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','[email protected]',2,1),
       (2,'Mack','Mack','[email protected]',1,1),
       (3,'Bob','Bobby','[email protected]',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

Самое главное помнить - попробовать все для себя, так как все, что мы предлагаем, может быть изменено факторами на вашем сайте, о которых мы не знаем.

Ответ 5

Как вы получаете доступ к базе данных? Проверьте сгенерированный SQL из этих запросов с помощью Profiler, если вы используете EF, например. Не устанавливайте соединение каждый раз в цикле foreach.

Я бы не создал html на стороне сервера. Просто верните объект для элемента управления источником данных.

Ответ 6

Убедитесь, что вы не подключаетесь к базе данных для каждого цикла.

Как я вижу, часть f.GetValue(u) - это метод, который возвращает строковое значение, полученное из базы данных.

Поместите данные в объект раз и навсегда и выполните то же самое, что и f.GetValue(u).

Ответ 7

Использовать индексированный для поля первичного ключа таблицы и в построителе построчных строк кода.

Ответ 8

Худшая проблема: тонны круговых поездок в базу данных. Каждый раз, когда вы получаете значение, запрос переходит по сети и ожидает результата.

Если вы сначала должны иметь список пользователей в коде, убедитесь, что:

  • Вы извлекаете всю информацию в списке пользователей в одном вызове db. Если у вас есть набор идентификаторов пользователей, вы можете отправить его с таблицей, оцененной параметр.
  • Если приведенное выше значение не содержит значения поля, отправьте список идентификаторов пользователей и список идентификаторов полей в 2 табличных оценках параметры, чтобы получить все это за один раз.

Это должно иметь огромное значение. С этими двумя конкретными запросами вы сняли сетевой шум и можете сосредоточиться на улучшении индексов, если это необходимо.

Еще один выигрыш, который вы получите, - это целые конкатенации строк. Первый шаг - заменить на StringBuilder. Следующим шагом будет прямое обращение к выходному потоку, поэтому вам не нужно хранить все эти данные в памяти... но маловероятно, что вам это понадобится; и если вы делаете это из-за слишком большого количества данных, у вас будут проблемы с обработкой браузеров, которые в любом случае.

пс. а не для сценария OP, но для тех, кто нуждается в скорости навалом, вы предпочитаете массовый экспорт: http://technet.microsoft.com/en-us/library/ms175937.aspx

Ответ 9

БЫСТРО... USE

  • хранимые процедуры
  • использовать считыватель

    SqlDataReader dbReader = mySqlCommand.ExecuteReader();
    
    //if reader has row values
    if (dbReader.HasRows) // while(xxx) for more rows return
    {
         //READ DATA
    }
    
  • СДЕЛАЙТЕ ПРОВОДНЫЕ ИНДЕКСЫ, если нужно перейти на разделы...

  • Использование и СОВЕТЫ для работы SELECT NOLOCK для меня

подсказки запросов (Transact-SQL) http://technet.microsoft.com/en-us/library/ms181714.aspx

Блокирующие подсказки http://technet.microsoft.com/en-us/library/aa213026(v=sql.80).aspx

Да, единственный раз, когда я буду использовать LINQ, будет, если я вызову хранимую процедуру.

Поиск LINQ to SQL

НО Я СТАРАЯ ШКОЛА....

Эта платформа Entity Framework я избавляюсь от них, поскольку Entity Framework 1.0 хорошо, когда вы делаете школьный проект...

Но очень дорого, как вычислительный экземпляр...

ПРОЧИТАЙТЕ ВСЕ В ПАМЯТИ ЧТО-ТО? ПОЧЕМУ Я ПЛАТИТ ДЛЯ SQL? ИСПОЛЬЗУЙТЕ некоторую структуру файлов JSON, затем...

Ответ 10

Вместо DataReader используйте DataAdapter и Dataset. Выполняйте все запросы один раз, как показано ниже:

  string SqlQuery ="Select * from Users;Select * From DataFields;Select * From DataFieldsValues;";

Это откроет sqlconnection один раз, только запустит все эти три запроса и вернет три таблицы данных в наборе данных, а затем применит ваш метод для рендеринга.