当前位置: 首页 > 科技观察

如何让SQL中的COUNT(-)飞起来

时间:2023-03-16 21:20:45 科技观察

本文转载自微信公众号《关于SQL》,作者是一只小鸟。转载本文,有关SQL公众号请联系。COUNT(*)是每个初学者的最爱,但是当你漂亮的回车,看着进度条一转一转的时候,总有一种莫名的愉悦感。总被老板催着做这做那,现在我可以命令电脑帮我跑数据了!虽然面试官总是喜欢问COUNT(*)有什么问题,但我为什么要避免使用COUNT(*)呢?问题。说实话,他们也一头雾水,因为面试题可能是从网上随便挑的。至于原理,又有多少人真正了解并真正关心呢?那么,COUNT(*)的性能真的那么差吗?我们怎样才能提高性能!让我们今天检查一下。我们知道SQLServer中有这么一张表(其他数据库也适用):CREATETABLE[dbo].[MobileLink]([user_id][varchar](50)NULL,[item_id][varchar](50)NULL,[behavior_type][varchar](50)NULL,[user_geohash][varchar](50)NULL,[item_category][varchar](50)NULL,[time][varchar](50)NULL)笨拙的堆表(HeapTable)这个表没有索引,它是一个堆表(HeapTable)。总共有超过4000万条数据。第一次运行count(*)SELECTCOUNT(*)ASCNTFROMdbo.MobileLink可以看到运行大约需要3秒。执行计划也简单,全表扫描的性能普遍。我之前分享过索引。数据存储在数据页上。这个数据页可以看作是一个页面。你在纸上写得越紧,你得到的信息就越多。反之,如果写得足够大,线条松散,每页能容纳的信息量就会少一些。所以,这样的全表扫描效率是很低的。理论上只要统计每一页每一行的第一个字段,就可以知道有多少行。所以索引就派上用场了。第一个提升性能的方案出来了。建立索引CREATEINDEXIDX_USR_ITEMONdbo.MobileLink(user_id,item_id);执行计划和我预料的一样,索引总耗时肯定是2.036s,比刚才的3s要好。经常在网上看到帖子说count的单列(比如count(user_id))会比count(*)有优势。这是真的?SELECTCOUNT(user_id)ASCNTFROMdbo.MobileLink2.813s与2.036s相比没有任何优势。快速提速-压缩那么按照刚才的思路,已经把user_id和item_id作为统计基数了,那么有没有办法让它变小呢?对,就是压缩ALTERINDEXIDX_USR_ITEMONdbo.MobileLinkREBUILDPARTITION=ALLWITH(DATA_COMPRESSION=PAGE);执行上面的Compress语句,然后运行??count(*)。结果与执行计划的比较耗时已经进入了1s级别,更进了一步。另一方面,使用单列(COUNT(user_id))统计行数:仍然徘徊在2s级别!可见COUNT(USER_ID)没有优势!SQLServer:我仍然可以有一个越来越快的方法,列索引。它的优点不仅是节省空间,还有压缩和双重优化。CREATENONCLUSTEREDCOLUMNSTOREINDEXCOL_IDXONdbo.MobileLink(user_id,item_id);已经突破了1s的水平。在列式索引面前,其他索引不得不让路!列式索引的结构比较复杂,详见这篇文章(SQLServerStorage)。这里提到列索引,分享一下列索引在存储和压缩方面的优势。对数据库的特性了解得越多,处理同一个问题的方法就越多。因此,我找不到理由不通读有关数据库系统的书籍。