由于您已经在 Url 列上创建了索引,因此我猜测是 SELECT(获取 pageid),那么如果它不存在,则 INSERT(新 URL)是导致 CPU 达到峰值的原因。如果你的爬虫有多个线程,你可能会对 tblPages 上的 SQL 中的并发/锁定机制征税。
关于您的具体问题,我会使用 CHECKSUM (crc) 而不是 HASHBYTES (md)。 CHECKSUM 更快,它返回 INT 而不是 VARBINARY,因此索引会更容易/更快。
但是,正是因为 CHECKSUM 返回一个 INT,所以很容易发生冲突,因此您还应该将 URL 作为 AND 子句进行搜索。
SELECT PageId FROM tblPages WHERE HashedUrl=CHECKSUM(@url) AND PageUrl=@url
现在只在 HashedUrl(而不是 PageUrl)上放置一个列索引。由于可能发生冲突,索引必须是非唯一的。这将为您提供最快的 INSERT 和 SELECT,直到您开始获得超过 40 亿的表行数,在这种情况下,INT CHECKSUM 冲突的数量将导致对未索引的 PageUrl 列进行大量的部分表扫描。
更新
这是我使用的简单基准代码
GO
/* NORMAL METHOD */
BEGIN
SET STATISTICS TIME ON
--
IF EXISTS(SELECT * FROM tempdb.dbo.sysobjects WHERE ID = OBJECT_ID(N'tempdb..#Store1'))
BEGIN
DROP TABLE #Store1
END
-- Normal
CREATE TABLE #Store1 (Id INT IDENTITY(1,1) PRIMARY KEY NONCLUSTERED, Data VARCHAR(4000))
CREATE UNIQUE CLUSTERED INDEX CIX_STORE1_DATA ON #Store1(Data)
-- Help Create Data
DECLARE @Data TABLE(Data VARCHAR(4000))
INSERT INTO @Data(Data) VALUES ('red.'), ('YELLOW/'), ('green'), ('.BLUE'), ('/violet'), ('PURPLE-'), ('-orange')
-- The data set we'll use for testing
INSERT INTO @Data
SELECT a.Data + b.Data + c.Data + d.Data + e.Data + f.Data + g.Data
FROM @Data a, @Data b, @Data c, @Data d, @Data e, @Data f, @Data g
-- INSERTION TESTS
PRINT('INSERT INTO NORMAL')
INSERT INTO #Store1(Data)
SELECT Data FROM @Data
-- SELECTION TESTS
PRINT('SELECT FROM NORMAL')
SELECT TOP 5000 d.Data, (SELECT s.Id FROM #Store1 s WHERE s.Data = d.Data) FROM @Data d
--
SET STATISTICS TIME OFF
END
GO
/* USING YOUR OWN CHECKSUM/HASH */
BEGIN
SET STATISTICS TIME ON
--
IF EXISTS(SELECT * FROM tempdb.dbo.sysobjects WHERE ID = OBJECT_ID(N'tempdb..#Store2'))
BEGIN
DROP TABLE #Store2
END
-- With Hash
CREATE TABLE #Store2 (Id INT IDENTITY(1,1) PRIMARY KEY NONCLUSTERED, Hsh INT, Data VARCHAR(4000))
CREATE CLUSTERED INDEX CIX_STORE2_CRC ON #Store2(Hsh)
-- Help Create Data
DECLARE @Data TABLE(Data VARCHAR(4000))
INSERT INTO @Data(Data) VALUES ('red.'), ('YELLOW/'), ('green'), ('.BLUE'), ('/violet'), ('PURPLE-'), ('-orange')
-- The data set we'll use for testing
INSERT INTO @Data
SELECT a.Data + b.Data + c.Data + d.Data + e.Data + f.Data + g.Data
FROM @Data a, @Data b, @Data c, @Data d, @Data e, @Data f, @Data g
-- INSERTION TESTS
PRINT('INSERT INTO CHECKSUM/HASH')
INSERT INTO #Store2(Hsh, Data)
SELECT CHECKSUM(Data), Data FROM @Data
-- SELECTION TESTS
PRINT('SELECT FROM CHECKSUM/HASH')
SELECT TOP 5000 d.Data, (SELECT s.Id FROM #Store2 s WHERE Hsh = CHECKSUM(d.Data) AND Data = d.Data) FROM @Data d
--
SET STATISTICS TIME OFF
END
结果(简而言之)我的方法实现更快(+30%)插入“经过时间 = 7339 毫秒”与“经过时间 = 10318 毫秒”,但是,较慢(-30%)选择“经过时间 = 37 毫秒”与“经过的时间 = 28 毫秒”。
另一个有趣的注意事项是您不能“正确”索引 URL VARCHAR 字段,因为长度(根据 http 规范 ~4kb)将大于 900 字节(SQL 2008 的最大允许密钥大小)。虽然 SQL 对此仅给出警告,但警告确实指出某些 INSERTS/UPDATES 可能会失败。
Warning! The maximum key length is 900 bytes. The index 'CIX_STORE1_DATA' has maximum length of 4000 bytes. For some combination of large values, the insert/update operation will fail.
我本身不是 SQL Guru,也许我的测试方法不是最准确/最有用的,但是关于不明智的用户端优化与“黑盒”相比,这个话题非常有趣。