Увеличьте скорость доступа к базе данных SQL Server для миллионов записей - PullRequest
1 голос
/ 10 марта 2011

Morning All,

У меня есть веб-сайт, над которым я работаю, около 2000 страниц кода, это сайт социальных сетей для бизнеса.У этого есть потенциал для миллионов пользователей.В настоящее время у нас около 80 000 пользователей, и доступ к сайту становится вялым.Я использую 98% хранимых процедур на сайте, чтобы улучшить скорость.Я хочу знать, что я могу сделать, чтобы улучшить скорость извлечения данных и увеличить время загрузки сайта.Насколько я знаю, таблица членов базы данных не использует полнотекстовое индексирование, будет ли это иметь значение?Я думаю, это было бы для поиска.Но, например, при входе в систему требуется некоторое время для загрузки.Вот сценарий входа в систему SP:

SELECT
a.MemberID,
CAST (ISNULL(a.ProfileTypeID,0) AS bit) AS HasProfile,
a.TimeOffsetDiff * a.TimeOffsetUnits AS TimeOffset,
b.City,
b.StateName AS State,
b.StateAbbr AS abbr,
b.Domain,
b.RegionID,
a.ProfileTypeID,
sbuser.sf_DisplayName(a.MemberID) AS DisplayName,
a.UserName,
a.ImgLib,
a.MemberREgionID AS HomeRegionID,
a.StateID,
a.IsSales,
a.IsAdmin
FROM Member a
INNER JOIN Region b ON b.RegionID = a.MemberRegionID
WHERE a.MemberID = @MemberID

UPDATE Member SET NumberLogins = (NumberLogins + 1) WHERE MemberID = @MemberID

Учитывая, что это охота только через 80 000 участников и может занять до 15 секунд для входа в систему, я считаю, что это очень медленно.Любые мысли о том, как я могу увеличить скорость входа в систему?

Очевидно, что извлечение списков участников на страницы также может быть трудоемким.Недавно я обновил устаревшие сценарии tf, которые содержали временные наборы данных и тому подобное, для подкачки страниц и заменил их следующим примером:

IF @MODE = 'MEMBERSEARCHNEW'
DECLARE @TotalPages INT
BEGIN
    SELECT @TotalPages = COUNT(*)/@PageSize
    FROM Member a
    LEFT JOIN State b ON b.StateID = a.StateID
    WHERE (sbuser.sf_DisplayName(a.MemberID) LIKE @UserName + '%')
    AND a.MemberID <> @MemberID;

    WITH FindSBMembers AS
    (
        SELECT ROW_NUMBER() OVER(ORDER BY a.Claimed DESC, sbuser.sf_MemberHasAvatar(a.MemberID) DESC) AS RowNum,
        a.MemberID,                                                -- 1
        a.UserName,                                                -- 2
        a.PrCity,                                                  -- 3
        b.Abbr,                                                    -- 4
        sbuser.sf_MemberHasImages(a.MemberID) AS MemberHasImages,  -- 5
        sbuser.sf_MemberHasVideo(a.MemberID) AS MemberHasVideo,    -- 6
        sbuser.sf_MemberHasAudio(a.MemberID) AS MemberHasAudio,    -- 7
        sbuser.sf_DisplayName(a.MemberID) AS DisplayName,          -- 8
        a.ProfileTypeID,                                           -- 9
        a.Zip,                                                     -- 10
        a.PhoneNbr,                                                -- 11
        a.PrPhone,                                                 -- 12
        a.Claimed,                                                 -- 13
        @TotalPages AS TotalPages                                  -- 14
        FROM Member a
        LEFT JOIN State b ON b.StateID = a.StateID
        WHERE (sbuser.sf_DisplayName(a.MemberID) LIKE @UserName + '%')
        AND a.MemberID <> @MemberID
    )
    SELECT * 
    FROM FindSBMembers
    WHERE RowNum BETWEEN (@PG - 1) * @PageSize + 1
    AND @PG * @PageSize
    ORDER BY Claimed DESC, sbuser.sf_MemberHasAvatar(MemberID) DESC
END

Есть ли еще какой-нибудь способ выжать еще большую скорость из этого сценария ..?

У меня были другие предложения, в том числе сжатие gzip, разбить таблицу Member на 26 таблиц на основе букв алфавита.Мне интересно знать, как это делают крупные компании, как они размещают свои данные, такие сайты, как Facebook, Yelp, Yellow Pages, Twitter.В настоящее время я работаю на сервере общего хостинга, может ли обновление до VPS или выделенного сервера помочь повысить скорость.

Сайт написан на классическом ASP с использованием SQL Server 2005.

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

С наилучшими пожеланиями и счастливого кодирования!

Пол

**** ДОПОЛНИТЕЛЬНЫЙ СТАРТ:

set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
GO



ALTER FUNCTION [sbuser].[sf_DisplayName](@MemberID bigint)
RETURNS varchar(150)

AS

BEGIN
DECLARE @OUT varchar(150)
DECLARE @UserName varchar(50)
DECLARE @FirstName varchar(50)
DECLARE @LastName varchar(50)
DECLARE @BusinessName varchar(50)
DECLARE @DisplayNameTypeID int

SELECT


    @FirstName = upper(left(FirstName, 1)) + right(FirstName, len(FirstName) - 1),
    @LastName = upper(left(LastName, 1)) + right(LastName, len(LastName) - 1) ,
    @BusinessName = upper(left(BusinessName, 1)) + right(BusinessName, len(BusinessName) - 1),
    @UserName = upper(left(UserName, 1)) + right(UserName, len(UserName) - 1),
    /*
    @FirstName = FirstName,
    @LastName = LastName,
    @BusinessName = BusinessName,
    @UserName = UserName,
    */
    @DisplayNameTypeID = DisplayNameTypeID
    FROM Member 
    WHERE MemberID = @MemberID

    IF @DisplayNameTypeID = 2   -- FIRST / LAST NAME
        BEGIN
            /*SET @OUT = @FirstName + ' ' + @LastName*/
            SET @OUT = @LastName + ', ' + @FirstName
        END
    IF @DisplayNameTypeID = 3 -- FIRST NAME / LAST INITIAL
        BEGIN
            SET @OUT = @FirstName + ' ' + LEFT(@LastName,1) + '.'
        END
    IF @DisplayNameTypeID = 4 -- BUSINESS NAME
        BEGIN
            SET @OUT = @BusinessName + ''
        END

    RETURN @OUT
END

**** ДОКЛАД КОНЕЦ

Ответы [ 8 ]

2 голосов
/ 10 марта 2011

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

создать индекс для таблицы элементов с memberid в качестве индекса и включить profiletypeid, timeoffsetdiff, timeoffsetunits, profiletypeid, memberid, имя пользователя, imglib, memberregionid, stateid, issales, isadmin.

также, jsut заметил вашу функцию sbuser.sf_DisplayName (a.memberID). Вы можете изучить эту функцию, чтобы убедиться, что это не ваша настоящая горлышко бутылки.

1 голос
/ 10 марта 2011

другой способ, если вы не хотите изменять какие-либо таблицы, это просто поместить логику udf в оператор выбора:

case DisplayNameTypeID
        when 2 then upper(left(LastName, 1)) + right(LastName, len(LastName) - 1) + ', ' + upper(left(FirstName, 1)) + right(FirstName, len(FirstName) - 1)
        when 3 then upper(left(FirstName, 1)) + right(FirstName, len(FirstName) - 1) + ' ' + upper(left(LastName, 1))
        when 4 then upper(left(BusinessName, 1)) + right(BusinessName, len(BusinessName) - 1)
    end as DisplayName

да, это выглядит немного ужасно, но все, что вам нужносделать, это изменить sp.

1 голос
/ 10 марта 2011

Быстрый и грязный способ убрать вызов UDF из "каждой строки"

SELECT *, sbuser.sf_DisplayName(MemberID) FROM (

    SELECT
    a.MemberID,
    CAST (ISNULL(a.ProfileTypeID,0) AS bit) AS HasProfile,
    a.TimeOffsetDiff * a.TimeOffsetUnits AS TimeOffset,
    b.City,
    b.StateName AS State,
    b.StateAbbr AS abbr,
    b.Domain,
    b.RegionID,
    a.ProfileTypeID,
    a.UserName,
    a.ImgLib,
    a.MemberREgionID AS HomeRegionID,
    a.StateID,
    a.IsSales,
    a.IsAdmin
    FROM Member a
    INNER JOIN Region b ON b.RegionID = a.MemberRegionID
    WHERE a.MemberID = @MemberID

)
1 голос
/ 10 марта 2011

Первый способ ускорить sf_DisplayName - добавить FirstName, LastName и т. Д. Из членов в качестве параметров и использовать их для построения DisplayName вместо поиска таблицы элементов.

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

Функция GetDisplayName должна быть создана с with schemabinding

create function dbo.GetDisplayName(
  @FirstName varchar(50),
  @LastName varchar(50),
  @DisplayNameType int) 
returns varchar(102) with schemabinding
as 
begin
  declare @Res varchar(102)
  set @Res = ''
  if @DisplayNameType = 1
    set @Res = @FirstName+' '+@LastName

  if @DisplayNameType = 2
    set @Res = @LastName+', '+@FirstName

  return @Res
end

Таблица с сохраненным столбцом DisplayName

CREATE TABLE [dbo].[Member](
    [ID] [int] NOT NULL,
    [FirstName] [varchar](50) NOT NULL,
    [LastName] [varchar](50) NOT NULL,
    [DisplayNameType] [int] NOT NULL,
    [DisplayName]  AS ([dbo].[GetDisplayName]([FirstName],[LastName],[DisplayNameType])) PERSISTED,
 CONSTRAINT [PK_Member] PRIMARY KEY CLUSTERED 
 (
    [ID] ASC
 )
)

Индекс на DisplayName

CREATE INDEX [IX_Member_DisplayName] ON [dbo].[Member] 
(
    [DisplayName] ASC
)

Вы также должны более внимательно посмотреть на то, что вы делаете в sf_MemberHasImages, sf_MemberHasVideo и sf_MemberHasAudio. Они используются в списке столбцов cte. Не так плохо, как в предложении where, но они все еще могут вызвать проблемы.

Последнее, что я обнаружил в качестве потенциальной проблемы, это sf_MemberHasAvatar. Он используется в order by в двух местах. Но порядок в row_number() используется как «где» из-за фильтрации в главном запросе «где» WHERE RowNum BETWEEN (@PG - 1) * @PageSize + 1.

Технику, описанную в постоянном столбце, можно использовать и в других функциях.

0 голосов
/ 10 марта 2011

Чтобы заменить UDF, если это является проблемой, я рекомендую иметь одно поле в таблице Member для хранения DisplayName, так как данные выглядят довольно статично с точки зрения вашей функции. Вам нужно обновить поле только один раз в начале, и с тех пор только тогда, когда кто-то регистрируется или DisplayNameTypeID изменяется. Я надеюсь, что это полезно для вас.

0 голосов
/ 10 марта 2011

@ Tom Gullen - В этом случае тот факт, что используется Classic ASP, может показаться неуместным, поскольку фактическая стоимость вычислений в этом случае, по-видимому, связана с SQL (или любой другой базой данных). Технология это работает).

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

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

Индексация!

0 голосов
/ 10 марта 2011

Исходные мысли

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

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

Время событий

Если вы уверены, что база данных работает медленно, используйте время в вашем скрипте. Добавьте общий таймер вверху страницы и таймер SQL.

Начальная загрузка страницы, инициализация общего времени. Когда вы достигнете хранимой процедуры, запустите таймер SQL. Когда запрос завершится, остановите таймер SQL. В конце страницы у вас есть два таймера, один из которых представляет общее время, потраченное на выполнение SQL, а другой таймер - таймер SQL дает вам общее время выполнения кода. Это помогает вам отделить вашу базу данных от вашего кода с точки зрения эффективности.

Улучшение производительности страницы ASP

Я подробно описал хороший дизайн ASP-страницы здесь:

Ошибка VBScript в нехватке памяти

Также рассмотрим:

  • Используйте Option Explicit вверху страниц.
  • Набор Response.Buffer = True
  • Используйте response.write внутри <%%>, многократно открывая и закрывая их медленно

Я еще раз повторю то, что я сказал в связанном ответе, безусловно, лучшее, что вы можете сделать для повышения производительности, - это сбросить результаты набора записей в массив с .getRows(). Не зацикливайте наборы записей. Не выбирайте поля в запросах, которые вы не используете. Только 1 набор записей и 1 соединение ado на страницу. Я действительно рекомендую вам прочитать ссылку для хорошего дизайна ASP-страницы.

Обновление, если нет явных проблем

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

0 голосов
/ 10 марта 2011

Поместить индексы на первичные и внешние ключи (MemberID, RegionID, MemberRegionID)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...