优化数据表的冗长SQL查询

时间:2016-02-07 21:28:14

标签: sql-server sql-server-2008 tsql

我想根据日期检索所有登录日志。另外我需要JQuery-datatable排序和搜索的所有功能!我曾经研究过许多查询和数据表,但这个问题比我想象的要困难。

CREATE PROCEDURE [dbo].[sp_login_logs]
(
    @sp_start_date DATETIME,
    @sp_end_date DATETIME,
    @sp_offset INT,
    @sp_count INT,
    @sp_search VARCHAR(MAX),
    @sp_sort INT
)
AS
BEGIN
    SELECT table1.email,table1.city,table1.latitude,table1.longitude,table1.first_log,
    table1.last_log,table1.platform,table1.app 
    FROM (SELECT ll.email,
       ISNULL(ll.city,'') city,
       ll.latitude,
       ll.longitude,
       (SELECT min(insertdate)
       FROM [LoginLog]
       WHERE email=ll.email) AS first_log,
       ll.insertdate AS last_log,
       CASE
           WHEN platform LIKE '%iPhone%'
                OR platform LIKE '%Darwin%'
                OR platform LIKE '%iPad%'
                OR platform LIKE '%iOS%' THEN 'iPhone'
           ELSE CASE
                    WHEN platform LIKE '%Android%'
                         OR platform LIKE '%Apache%' THEN 'Android'
                    ELSE 'iPhone'
                END
       END AS platform,
       CASE
           WHEN app IS NULL THEN 'Consumer'
           ELSE App
       END AS app
    FROM [LoginLog] ll
    WHERE id =
        (SELECT max(id)
         FROM [LoginLog] ll2
         WHERE ll2.email =ll.email
         AND
            (ll2.email like '%'+@sp_search+'%'OR
            ll2.city like '%'+@sp_search+'%'OR
            ll2.latitude like '%'+@sp_search+'%'OR
            ll2.longitude like '%'+@sp_search+'%'
            )
         )
         AND ll.email<>'' AND ll.email<>'(null)'
         AND ll.insertdate>@sp_start_date AND ll.insertdate<@sp_end_date
         AND loginsucess=1 and isnull(Country, 'United States')='United States'
    ) AS table1
    WHERE(
            table1.first_log like '%'+@sp_search+'%'OR
            table1.last_log like '%'+@sp_search+'%'OR
            table1.platform like '%'+@sp_search+'%'OR
            table1.app like '%'+@sp_search+'%'          
        )
    ORDER BY
        CASE WHEN (@sp_sort%100 = 01 and ((@sp_sort%1000)/100) = 1) THEN table1.email END ASC,
        CASE WHEN (@sp_sort%100 = 01 and ((@sp_sort%1000)/100) = 0) THEN table1.email END DESC,
        CASE WHEN (@sp_sort%100 = 02 and ((@sp_sort%1000)/100) = 1) THEN table1.city END ASC,
        CASE WHEN (@sp_sort%100 = 02 and ((@sp_sort%1000)/100) = 0) THEN table1.city END DESC,
        CASE WHEN (@sp_sort%100 = 03 and ((@sp_sort%1000)/100) = 1) THEN table1.latitude END ASC,
        CASE WHEN (@sp_sort%100 = 03 and ((@sp_sort%1000)/100) = 0) THEN table1.latitude END DESC,
        CASE WHEN (@sp_sort%100 = 04 and ((@sp_sort%1000)/100) = 1) THEN table1.longitude END ASC,
        CASE WHEN (@sp_sort%100 = 04 and ((@sp_sort%1000)/100) = 0) THEN table1.longitude END DESC,
        CASE WHEN (@sp_sort%100 = 05 and ((@sp_sort%1000)/100) = 1) THEN table1.first_log END ASC,
        CASE WHEN (@sp_sort%100 = 05 and ((@sp_sort%1000)/100) = 0) THEN table1.first_log END DESC,
        CASE WHEN (@sp_sort%100 = 06 and ((@sp_sort%1000)/100) = 1) THEN table1.last_log END ASC,
        CASE WHEN (@sp_sort%100 = 06 and ((@sp_sort%1000)/100) = 0) THEN table1.last_log END DESC,
        CASE WHEN (@sp_sort%100 = 07 and ((@sp_sort%1000)/100) = 1) THEN table1.platform END ASC,
        CASE WHEN (@sp_sort%100 = 07 and ((@sp_sort%1000)/100) = 0) THEN table1.platform END DESC,      
        CASE WHEN (@sp_sort%100 = 08 and ((@sp_sort%1000)/100) = 1) THEN table1.app END ASC,
        CASE WHEN (@sp_sort%100 = 08 and ((@sp_sort%1000)/100) = 0) THEN table1.app END DESC        
    OFFSET @sp_offset ROWS
    FETCH NEXT @sp_count ROWS Only
END

这样可以正常运行但耗费大量内存和时间......不能等待5分钟,其中有超过数百万条记录。

如果有人需要,这是我的表格:

CREATE TABLE [dbo].[LoginLog](
    [ID] [bigint] IDENTITY(1,1) NOT NULL,
    [Email] [nvarchar](max) NULL,
    [Platform] [nvarchar](max) NULL,
    [Latitude] [nvarchar](max) NULL,
    [Longitude] [nvarchar](max) NULL,
    [InsertDate] [datetime] NOT NULL,
    [ModifiedDate] [datetime] NULL,
    [ipaddress] [nvarchar](55) NULL,
    [City] [varchar](50) NULL,
    [APP] [varchar](55) NULL,
    [Country] [varchar](55) NULL,
PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)

谢谢!

2 个答案:

答案 0 :(得分:1)

我认为你无能为力。问题在于排序中的情况如果要杀死sql server可能做的任何优化。您应该查看查询计划,并添加一个重新编译,但在一天结束时 - 该查询不会有效。动态SQL是唯一有效的方法 - 从客户端,或通过sp中的字符串操作,然后执行命令来执行SQL字符串。

显然,非sargeable元素会杀死任何索引的使用 - 最后,设计数据库的人在这里做了一个非常无能的工作。没有正确的方法来有效地查询它。

答案 1 :(得分:1)

正如我在评论部分中提到的,您的存储过程存在许多问题,因此很难指出一件事并说:“在这里,这就是问题所在,修复它并且你很好”。

我会列出一些跳出来的东西:

  

派生表混乱中的派生表中的派生表。

编译器通常擅长优化查询,前提是查询在派生表/子查询方面不是太深(即不太复杂)。如果您的查询变得太深,您应该考虑在临时表中填充派生表(如果需要,可以适当地编入索引)。

我知道这是一个非常广泛的陈述,很难确定你何时应该采用这种工作方式。像往常一样,证据就是吃布丁。

  

强制表扫描会破坏性能。

例如,您有一个确定MAX(id) FROM [LoginLog] ll2的子查询。在该子查询中,WHERE子句的条件为ll2.email=ll.email。如果LoginLog.email上没有合适的INDEX,则会强制LoginLog上的表扫描以查找相应的电子邮件地址。

该子句具有一个额外的复杂查找,其中包含一系列OR'ed LIKE语句,这些语句将强制进行表扫描。 SQL Server中没有布尔短路,因此即使您在LoginLog.email上提供INDEX,也可能会进行表扫描以确定其他条件的状态。

如果您的查询包含Actual Execution Plan,则可以看到这些扫描。

  

尝试在一个查询中完成所有操作

完成所有操作的查询通常过于复杂而无法快速执行。考虑拆分用例,并为每个这样的用例创建一个更简单的查询。通常,具有少量参数的非复杂查询将更快地执行。

  

参数嗅探

我不打算详细介绍,网上有很多文章可以解释这一点(例如this one首先出现在我的搜索引擎中)。这种“参数嗅探”会损害存储过程的性能。第一次运行存储过程时,SQL Server编译器将创建一个针对传递给它的参数进行优化的执行计划。缓存这些编译的执行计划,以便编译器不必在每次执行时重新编译存储过程。存储过程的执行计划将在后续调用中重用,但是此执行计划对于其他参数可能完全没有效率。解决此问题的一种方法是为您的查询指定OPTION(OPTIMIZE FOR UNKNOWN)

  

其他评论

  1. ORDER BY子句太宽,如果结果集很大,可能会损害性能。您应该考虑创建动态SQL以仅对实际需要的列进行排序。

  2. 您正在使用@sp_search查找first_loglast_logplatformapp中的文字...这看起来很傻,什么用例应该在如此多的文本列中找到意味着不同的文本?

  3. 没有索引(至少你没有显示任何索引)。如果查询表,则应提供合适的索引以加快它们的速度。否则,除非您要查找ID,否则您将强制进行表扫描。