提高数百万条记录的SQL Server数据库访问速度

时间:2011-03-10 16:31:16

标签: sql sql-server tsql

早上好,

我有一个我正在研究的网站,大约有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,黄页,Twitter等网站。我目前正在共享托管服务器上运行,升级到VPS或专用服务器有助于提高速度。

该网站使用SQL Server 2005以经典ASP编写。

非常感谢任何人提供的任何帮助。

最诚挚的问候和快乐的编码!

**** ADDITION START:

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

**** ADDITION END

8 个答案:

答案 0 :(得分:2)

80000不是很多记录,除非你没有索引,或者你的数据类型很大。如果该查询确实是您的下一个瓶子,那么您可能需要考虑在成员表和区域表上创建覆盖索引。

使用memberid作为索引在成员表上创建索引,并包括profiletypeid,timeoffsetdiff,timeoffsetunits,profiletypeid,memberid,username,imglib,memberregionid,stateid,issales,isadmin。

另外,jsut注意到你的函数sbuser.sf_DisplayName(a.memberID)。你可能会探索这个功能,以确保那不是你真正的瓶颈。

答案 1 :(得分:1)

加速sf_DisplayName的第一个选项是从成员中添加FirstName,LastName等作为参数,并使用它来构建DisplayName而不是对成员表进行查找。

之后,您可以考虑将DisplayName作为计算和持久列添加到成员表中。这意味着将在保存成员时计算DisplayName,并在执行查询时使用保存的值。您还可以在DisplayName列上添加索引。

必须使用with schemabinding

创建GetDisplayName函数
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_MemberHasImagessf_MemberHasVideosf_MemberHasAudio中所做的工作。它们用在cte的列列表中。没有在where子句中使用的那么糟糕,但它们仍然可能会给你带来问题。

我发现的最后一个潜在问题是sf_MemberHasAvatar。它在两个地方的order by中使用。但是row_number()中的顺序用作where,因为主查询where where WHERE RowNum BETWEEN (@PG - 1) * @PageSize + 1中的过滤。

使用持久列描述的技术也可以用于其他功能。

答案 2 :(得分:1)

快速肮脏的方式从“每一行”中取出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

)

答案 3 :(得分:1)

另一种方式,如果你不想修改任何表,只需将udf逻辑放在select语句中:

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
是的,它看起来有点gorey,但你所要做的就是修改sp。

答案 4 :(得分:0)

indexes放在主键和外键(MemberID,RegionID,MemberRegionID)

答案 5 :(得分:0)

初步想法

这里的问题可能与您的存储过程无关。特别是在登录脚本方面,您将注意力集中在一个小而无关的地方,因为登录命令是一次性成本,并且您可以对这类页面的脚本执行时间有更高的容忍度。

您使用的是经典ASP,现在已经过时了。当您与这么多访问者打交道时,您的服务器将需要很多权力来管理它正在解释的所有请求。解释的页面将比编译的页面运行得慢。

时间事件

如果您确信数据库速度很慢,请在脚本中使用时间。在页面顶部添加一个通用计时器和一个SQL计时器。

页面启动加载,初始化一般时间。到达存储过程时,启动SQL计时器。查询完成后,停止SQL计时器。在页面的末尾,您有两个计时器,一个计算运行SQL所花费的时间,另一个计时器 - SQL计时器为您提供执行代码的总时间。这有助于您在效率方面将数据库与代码分开。

提高ASP页面效果

我在这里详细介绍了好的ASP页面设计:

VBScript Out Of Memory Error

还要考虑:

  • 使用页面顶部的Option Explicit
  • 设置Response.Buffer = True
  • 在&lt; %%&gt;内使用response.write,反复打开和关闭这些是慢的

我将重新迭代我在链接答案中所说的内容,到目前为止,您可以为性能做的最好的事情是将记录集结果转储到.getRows()的数组中。不要循环记录集。不要在不使用的查询中选择字段。每页只有1个记录集和1个ado连接。我真的建议你阅读好的ASP页面设计链接。

如果没有明显问题则升级

服务器的规格是什么?升级硬件可能是在这种情况下提高性能的最佳途径,并且在成本/奖励方面效率最高。

答案 6 :(得分:0)

@Tom Gullen - 在这种情况下,使用Classic ASP的事实似乎是无关紧要的,因为在这个实例中计算方面的实际成本似乎是SQL(或者其他) db tech正在运行。)

@the question - 我同意Cosmin的意见,即对表格中的相关字段进行索引会提供明确的性能提升,假设它们尚未完成。

我们在大约一周前遇到过这个案例,我的老板试图从批处理文件中进行多次条件插入,这是永久性的。我们在userid字段上放置一个索引,嘿,presto,相同的脚本花了大约一分钟来执行。

<强>索引!

答案 7 :(得分:0)

要替换UDF,如果这是问题,我建议在Member表中使用一个字段来存储DisplayName,因为数据似乎与函数的外观相当静态。您只需要在开头更新字段一次,然后仅在有人注册或DisplayNameTypeID更改时才更新。我希望这对你有所帮助。