UNION或UNION是否都构建了一个锁定所有选定表的大型查询?

时间:2016-02-08 22:26:10

标签: sql-server

我的主管DBA告诉我,我编写了不良格式的代码,因为我使用UNION ALL在不同的表上累积连续查询的结果。我想当一个带有多个select语句的查询具有单独执行UNIONed的结果时,所以当每个select语句执行时,它会在完成后释放的表上放置一个共享锁,然后开始下一个select。 我以为结果是在一些缓冲区或tmp表中累积的。

有人会告诉我幕后发生了什么,以及当一百个选择语句的结果是UNION时所消耗的资源。每个select都在一个表上运行,并收集模式,表和列名称。

抱歉,我没有查询计划。 DBA抱怨说查询太大,无法显示大部分计划。他的评论低于查询。

SELECT 'R_Stage' as TheSchema, 'DateFrozenSectionModF63x086' as TheTable, 'PersonModTextStaffSID' as TheColumn, COUNT(*) as NullCount 
FROM [R_Stage].[DateFrozenSectionModF63x086] WHERE [PersonModTextStaffSID] = -1  
UNION ALL 
SELECT 'R_Stage' as TheSchema, 'DateFrozenSectionModF63x086' as TheTable, 'LabDataLabSubjectSID' as TheColumn, COUNT(*) as NullCount 
FROM [R_Stage].[DateFrozenSectionModF63x086] WHERE [LabDataLabSubjectSID] = -1  
UNION ALL 
SELECT 'R_Stage' as TheSchema, 'DateFrozenSectionModF63x086' as TheTable, 'LabDataPatientSID' as TheColumn, COUNT(*) as NullCount 
FROM [R_Stage].[DateFrozenSectionModF63x086] WHERE [LabDataPatientSID] = -1  
UNION ALL 
SELECT 'R_Stage' as TheSchema, 'DateGrossDescChangedF63x087' as TheTable, 'PersonModTextStaffSID' as TheColumn, COUNT(*) as NullCount 
FROM [R_Stage].[DateGrossDescChangedF63x087] WHERE [PersonModTextStaffSID] = -1  
UNION 
ALL SELECT 'R_Stage' as TheSchema, 'DateGrossDescChangedF63x087' as TheTable, 'LabDataLabSubjectSID' as TheColumn, COUNT(*) as NullCount 
FROM [R_Stage].[DateGrossDescChangedF63x087] WHERE [LabDataLabSubjectSID] = -1  
UNION ALL 
SELECT 'R_Stage' as TheSchema, 'DateGrossDescChangedF63x087' as TheTable, 'LabDataPatientSID' as TheColumn, COUNT(*) as NullCount 
FROM [R_Stage].[DateGrossDescChangedF63x087] WHERE [LabDataPatientSID] = -1  
UNION ALL 

无论如何,上面的查询肯定能以更有效的方式编写。正如为查询中的每个表所写的那样,它将扫描整个表中每个UNION的791次。只需查看查询的前几行,我们就可以看到这些只是来自同一个表的计数,这可以通过使用CASE表达式对该表进行单次扫描来完成,并且您将获得所有计数每桌通过。 最重要的是,现在我们在FRE上只有少数用户,这样的流程已经影响了许多用户/工作。想象一下,当我们有成百上千的用户时。我们根本无法承担像这两个例子一样运行没有经过审查或正确测试的流程。这不是个人的,不应该这样,它完全取决于服务器和所有用户的整体健康状况。指出这些问题是我工作的一部分,所以当我看到它们时可以解决它们,这无疑是其中一个时代。这些内容在重写之前不能再次运行,以确保它们能够完成预期的操作,并且它们足够高效,不会导致其他进程出现问题。

2 个答案:

答案 0 :(得分:4)

您的DBA的建议似乎很合理。他/她没有提到锁定,并且不清楚为什么你提到这个问题。

正如DBA所述,您正在执行791个查询,数据库引擎随后将它们联合在一起。这将对数据库施加负担。假设您的DBA对于那些作为全表扫描的查询是正确的,这意味着整个表将被读取791次。

无论是否锁定,都会破坏磁盘,超出文件系统和数据库缓存,并加载运行这些查询的CPU。

假设您的数据库足够大以至于它不适合RAM文件系统或数据库缓存,这意味着每次都必须从磁盘中读取它。 如果查询被重写为您的DBA建议,以便它只通过数据库进行1次全表扫描,则对文件系统的影响将是当前写入的查询的1/791。

如果您的数据库确实同时采用了读锁定,那么您的查询将会影响该表的更新者791次。

您的DBA建议可以使提议的查询效率大约提高791倍。

如果我们假设您的表格为100兆字节,在磁盘读取速度为100 mb / s时,处理791个查询中的每个查询需要大约1秒钟,因此完整查询大约需要14分钟。重写为DBA建议,大约需要1秒钟。

这不是一个锁定问题,它是一个典型的I / O性能问题。如果您也遇到锁定问题,那就会让情况变得更糟。

查询的确切性能特征取决于许多因素,包括表的大小,定义了哪些索引(注意索引可以在某些情况下使查询更慢),如何扩展'表格是,表格中的列类型,运行查询的硬件,使用的数据库系统,磁盘的速度,数据库的RAM大小,系统上发生的其他情况,以及上。因此,如果没有更多信息,就无法给出明确的答案。

但是避免791全表扫描是提高性能的良好开端。

答案 1 :(得分:1)

对不起,这篇文章让我的眼睛受伤了。听起来你需要编写一个脚本来清理或识别问题。为了简化这一点您可以自动执行脚本,在发布这300个表之前将吐出小的可测试的sql语句代码。如果您的dba将允许您使用游标和临时表,但是在可能的情况下应该避免使用它们,这似乎更像是识别问题和/或清理问题而不是关注效率。话虽这么说,我不想在生产系统上锁定那些表一段时间......所以做了很多小任务来减少锁定并达到相同的目标。您可以在sql server admin中运行此脚本并将输出复制为输入以提供给您的dba,也许它会有所帮助。

SET NOCOUNT ON

DECLARE  @OUTPUT TABLE
(  
    TheSchema NVARCHAR(45),
    TheTable NVARCHAR(45),
    Field1 NVARCHAR(45),
    Field2 NVARCHAR(45),
    Field3 NVARCHAR(45)
)

INSERT @OUTPUT SELECT 'R_Stage','DateFrozenSectionModF63x086','PersonModTextStaffSID','LabDataLabSubjectSID','LabDataPatientSID'
INSERT @OUTPUT SELECT 'R_Stage','DateFrozenSectionModF63x087','PersonModTextStaffSID','LabDataLabSubjectSID','LabDataPatientSID'
INSERT @OUTPUT SELECT 'R_Stage','DateFrozenSectionModF63x088','PersonModTextStaffSID','LabDataLabSubjectSID','LabDataPatientSID'
INSERT @OUTPUT SELECT 'R_Stage','DateFrozenSectionModF63x089','PersonModTextStaffSID','LabDataLabSubjectSID','LabDataPatientSID'
INSERT @OUTPUT SELECT 'R_Stage','DateFrozenSectionModF63x090','PersonModTextStaffSID','LabDataLabSubjectSID','LabDataPatientSID'
INSERT @OUTPUT SELECT 'R_Stage','DateFrozenSectionModF63x091','PersonModTextStaffSID','LabDataLabSubjectSID','LabDataPatientSID'
INSERT @OUTPUT SELECT 'R_Stage','DateFrozenSectionModF63x092','PersonModTextStaffSID','LabDataLabSubjectSID','LabDataPatientSID'
INSERT @OUTPUT SELECT 'R_Stage','DateFrozenSectionModF63x093','PersonModTextStaffSID','LabDataLabSubjectSID','LabDataPatientSID'
INSERT @OUTPUT SELECT 'R_Stage','DateFrozenSectionModF63x094','PersonModTextStaffSID','LabDataLabSubjectSID','LabDataPatientSID'
INSERT @OUTPUT SELECT 'R_Stage','DateFrozenSectionModF63x095','PersonModTextStaffSID','LabDataLabSubjectSID','LabDataPatientSID'



DECLARE @TheSchema NVARCHAR(45),@TheTable NVARCHAR(45),@Field1 NVARCHAR(45),@Field2 NVARCHAR(45),@Field3 NVARCHAR(45)

DECLARE LOOP CURSOR FOR
SELECT TheSchema,TheTable,Field1,Field2,Field3 FROM @OUTPUT


PRINT '

IF (EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES  WHERE  TABLE_NAME = ''__MY_SCAN''))
DROP TABLE __MY_SCAN


CREATE TABLE  __MY_SCAN(
    TheShema NVARCHAR(45),
    TheTable NVARCHAR(45),
    Field1NullCount INT,
    Field2NullCount INT,
    Field3NullCount INT
)'

OPEN LOOP 
FETCH NEXT FROM LOOP INTO @TheSchema,@TheTable,@Field1,@Field2,@Field3
WHILE(@@FETCH_STATUS=0) BEGIN
    PRINT 
        'INSERT __MY_SCAN
            SELECT 
                '''+@TheSchema+''' AS '+@TheSchema+',
                '''+@TheTable+''' AS '+@TheTable+',
                COUNT(Field1),
                COUNT(Field2),
                COUNT(Field3)
            FROM
            (   
                SELECT       
                    Field1=CASE WHEN '+@Field1+'=-1 THEN 1 ELSE 0 END,
                    Field2=CASE WHEN '+@Field2+'=-1 THEN 1 ELSE 0 END,
                    Field3=CASE WHEN '+@Field3+'=-1 THEN 1 ELSE 0 END
                FROM
                    '+@TheTable+'
                WHERE
                    '+@Field1+'=-1 OR '+@Field2+'=-1 OR '+@Field3+'=-1
            )AS X
            GO'

    FETCH NEXT FROM LOOP INTO @TheSchema,@TheTable,@Field1,@Field2,@Field3
END
CLOSE LOOP
DEALLOCATE LOOP

PRINT '
SELECT * FROM __MY_SCAN 
GO
DROP TABLE __MY_SCAN
GO
'