如何优化此 SQL 查询
Posted
技术标签:
【中文标题】如何优化此 SQL 查询【英文标题】:How can I optimise this SQL query 【发布时间】:2011-12-13 13:06:25 【问题描述】:我正在编写一个软件,用于识别已放在网络服务器 (CMS) 上但不再需要且应该/可以删除的文件。
首先,我尝试手动重现所有必需的步骤。
我正在使用在 webroot 中执行的批处理脚本来识别服务器上的所有(相关)文件。然后,我将列表导入 SQL Server,表格如下所示:
id filename
1 filename1.docx
2 files/file.pdf
3 files/filename2.docx
4 files/filename3.docx
5 files/file1.pdf
6 file2.pdf
7 file4.pdf
我还有一个 CMS 数据库(Alterian/Immediate CMC 6.X),它有 2 个存储页面内容的表:page_data 和 PageXMLArchive。
我想扫描数据库以查看第一个表中的文件是否在站点内容中的任何位置引用 - page_data 表中的 p_content 列和 PageXMLArchive 表中的 PageXML 列。
所以我有一个循环,它获取每个文件名并检查它是否在这些表中的任何一个中被引用,如果是则跳过它,如果不是则将其添加到临时表中。
在查询结束时显示临时表。
以下查询:
DECLARE @t as table (_fileName nvarchar(255))
DECLARE @row as int
DECLARE @result as nvarchar(255)
SET @row = 1
WHILE(@row <= (SELECT COUNT(*) FROM ListFileReport))
BEGIN
SET @result = (SELECT [FileName] FROM ListFileReport WHERE id = @row)
IF ((SELECT TOP(1) p_content FROM page_data WHERE p_content LIKE '%' + LTRIM(RTRIM(@result)) + '%') IS NULL) OR ((SELECT TOP(1) PageXML FROM PageXMLArchive WHERE PageXML LIKE '%' + LTRIM(RTRIM(@result)) + '%') IS NULL)
BEGIN
INSERT INTO @t (_fileName) VALUES(@result)
END
SET @row = @row + 1
END
select * from @t
不幸的是,由于我的 SQL 技能不佳,查询需要 2 多个小时才能执行并超时。
如何改进该查询,或更改它以实现类似的事情,而不必在 ntext 字段上运行 1000 条 WHERE x LIKE 语句?我无法对数据库进行任何更改,它必须保持不变(否则它将不受支持 - 对我们的客户来说很重要)。
谢谢
编辑: 目前我正在通过一次批处理几百个结果来解决这个问题。它有效,但需要很长时间。
编辑:
我可以利用全文搜索来实现这一点吗?如果有办法更改架构以实现预期结果,我愿意拍摄数据库快照并处理副本。
page_data 表:
USE [TD-VMB-01-STG]
GO
/****** Object: Table [dbo].[page_data] Script Date: 12/13/2011 13:19:15 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[page_data](
[p_page_id] [int] NOT NULL,
[p_title] [nvarchar](120) NULL,
[p_link] [nvarchar](250) NULL,
[p_content] [ntext] NULL,
[p_parent_id] [int] NULL,
[p_top_id] [int] NULL,
[p_stylesheet] [nvarchar](50) NULL,
[p_author] [nvarchar](50) NULL,
[p_last_update] [datetime] NULL,
[p_order] [smallint] NULL,
[p_window] [nvarchar](10) NULL,
[p_meta_keywords] [nvarchar](1000) NULL,
[p_meta_desc] [nvarchar](2000) NULL,
[p_type] [nvarchar](1) NULL,
[p_confirmed] [int] NOT NULL,
[p_changed] [int] NOT NULL,
[p_access] [int] NULL,
[p_errorlink] [nvarchar](255) NULL,
[p_noshow] [int] NOT NULL,
[p_edit_parent] [int] NULL,
[p_hidemenu] [int] NOT NULL,
[p_subscribe] [int] NOT NULL,
[p_StartDate] [datetime] NULL,
[p_EndDate] [datetime] NULL,
[p_pageEnSDate] [int] NOT NULL,
[p_pageEnEDate] [int] NOT NULL,
[p_hideexpiredPage] [int] NOT NULL,
[p_version] [float] NULL,
[p_edit_order] [float] NULL,
[p_order_change] [datetime] NOT NULL,
[p_created_date] [datetime] NOT NULL,
[p_short_title] [nvarchar](30) NULL,
[p_authentication] [tinyint] NOT NULL,
CONSTRAINT [aaaaapage_data_PK] PRIMARY KEY NONCLUSTERED
(
[p_page_id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
ALTER TABLE [dbo].[page_data] ADD CONSTRAINT [DF_page_data_p_order] DEFAULT (0) FOR [p_order]
GO
ALTER TABLE [dbo].[page_data] ADD CONSTRAINT [DF__Temporary__p_con__1CF15040] DEFAULT (0) FOR [p_confirmed]
GO
ALTER TABLE [dbo].[page_data] ADD CONSTRAINT [DF__Temporary__p_cha__1DE57479] DEFAULT (0) FOR [p_changed]
GO
ALTER TABLE [dbo].[page_data] ADD CONSTRAINT [DF__Temporary__p_acc__1ED998B2] DEFAULT (1) FOR [p_access]
GO
ALTER TABLE [dbo].[page_data] ADD CONSTRAINT [DF__Temporary__p_nos__1FCDBCEB] DEFAULT (0) FOR [p_noshow]
GO
ALTER TABLE [dbo].[page_data] ADD CONSTRAINT [DF__Temporary__p_edi__20C1E124] DEFAULT (0) FOR [p_edit_parent]
GO
ALTER TABLE [dbo].[page_data] ADD CONSTRAINT [DF__Temporary__p_hid__21B6055D] DEFAULT (0) FOR [p_hidemenu]
GO
ALTER TABLE [dbo].[page_data] ADD CONSTRAINT [DF_page_data_p_subscribe] DEFAULT (0) FOR [p_subscribe]
GO
ALTER TABLE [dbo].[page_data] ADD CONSTRAINT [DF_page_data_p_pageEnSDate] DEFAULT (0) FOR [p_pageEnSDate]
GO
ALTER TABLE [dbo].[page_data] ADD CONSTRAINT [DF_page_data_p_pageEnEDate] DEFAULT (0) FOR [p_pageEnEDate]
GO
ALTER TABLE [dbo].[page_data] ADD CONSTRAINT [DF_page_data_p_hideexpiredPage] DEFAULT (1) FOR [p_hideexpiredPage]
GO
ALTER TABLE [dbo].[page_data] ADD CONSTRAINT [DF_page_data_p_version] DEFAULT (0) FOR [p_version]
GO
ALTER TABLE [dbo].[page_data] ADD CONSTRAINT [DF_page_data_p_edit_order] DEFAULT (0) FOR [p_edit_order]
GO
ALTER TABLE [dbo].[page_data] ADD CONSTRAINT [DF_page_data_p_order_change] DEFAULT (getdate()) FOR [p_order_change]
GO
ALTER TABLE [dbo].[page_data] ADD CONSTRAINT [DF_page_data_p_created_date] DEFAULT (getdate()) FOR [p_created_date]
GO
ALTER TABLE [dbo].[page_data] ADD CONSTRAINT [DF_page_data_p_authentication] DEFAULT ((0)) FOR [p_authentication]
GO
PageXMLArchive 表:
USE [TD-VMB-01-STG]
GO
/****** Object: Table [dbo].[PageXMLArchive] Script Date: 12/13/2011 13:20:00 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[PageXMLArchive](
[ArchiveID] [bigint] IDENTITY(1,1) NOT NULL,
[P_Page_ID] [int] NOT NULL,
[p_author] [nvarchar](100) NULL,
[p_title] [nvarchar](400) NULL,
[Version] [int] NOT NULL,
[PageXML] [ntext] NULL,
[ArchiveDate] [datetime] NOT NULL,
CONSTRAINT [PK_PageXMLArchive] PRIMARY KEY CLUSTERED
(
[ArchiveID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
ALTER TABLE [dbo].[PageXMLArchive] ADD CONSTRAINT [DF_PageXMLArchive_ArchiveDate] DEFAULT (getdate()) FOR [ArchiveDate]
GO
【问题讨论】:
您能否在问题中包含您的 page_data 和 PageXMLArchive 表的结构? 对于初学者,您应该将SELECT COUNT(*)
移动到WHILE
上方并将结果放入变量中。避免对每一行进行该查询。
@MarkBannister 添加了表结构。谢谢
@Johan 谢谢。将 SELECT COUNT(*) 移到 WHILE 循环之上并没有什么区别。刚刚运行查询。 :(
【参考方案1】:
你可以通过多种方式避免循环,这里有一个例子......
SELECT
*
FROM
ListFileReport
WHERE
NOT EXISTS (
SELECT *
FROM page_data
WHERE p_content LIKE '%' + LTRIM(RTRIM(ListFileReport.FileName)) + '%'
)
AND
NOT EXISTS (
SELECT *
FROM PageXMLArchive
WHERE PageXML LIKE '%' + LTRIM(RTRIM(ListFileReport.FileName)) + '%'
)
注意:这消除了循环,因此会产生巨大的改进。但它仍然必须为 ListFileReport 中的每个条目解析整个两个查找表,没有任何巧妙的算法,因为它们可能没有有用的索引。所以它仍然会像狗一样慢,它只会断一条腿而不是两条。
避免使用 LIKE 的唯一方法是解析 page_data
和 PageXMLArchive
表中的所有字段并创建引用文件列表。由于 html 和 XML 是非常结构化的,这可以做到,但我会寻找一个库或其他东西来为你做。
然后,您可以创建另一个表,其中包含所有文件,没有重复,并具有适当的索引。查询而不是使用 LIKE 会更快。我一点也不怀疑。但是编写或查找代码将是一件苦差事。
【讨论】:
谢谢。出于测试目的,我运行了查询(仅查询 page_data 表的一部分,将 PageXMLArchive 排除在外),我的查询在 5:04 分钟内完成,而您的查询需要 5:09 分钟。这仅在 page_data 表(1600 行)上。 PageXMLArchive 是 16000 行。将 COUNT 查询移出循环并立即运行。稍后会报告。【参考方案2】:一个存储过程尤其有一个循环,其中select
和insert
混合会明显减慢查询速度。
如果你可以insert into @table select a, b from table
理想情况下,它将比单独插入每一行快数百万倍。
对于您的示例,可以执行以下操作:
insert into @t (_fileName) select ... from p_content join ...on .. where sth like %sth
如果不适用,请告诉我。
【讨论】:
我不认为这是导致延迟的插入。这是 SELECT * FROM X WHERE Y LIKE '%Z%' 在 2 个表中的 ntext 列上,总共 17000 行。此外,每一行的 ntext 字段将包含整个 XML/HTML 代码页面。以上是关于如何优化此 SQL 查询的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 JOINS 和嵌套 SELECT 优化此 SQL 查询?