第13/24周 统计信息

欢迎来到性能调优培训的第4个月。这个月全是关于SQL Server里的统计信息,还有它们如何帮助查询优化器生成足够好的执行计划。统计信息主要是被查询优化器用来估计查询返回的行数。它只是个估计,没别的。

统计信息概述

SQL Server使用在统计信息对象里称作直方图(Histogram)的东西,它描述了对于所给列最大200步长(Steps)的数据分布情况。最大的局限性之一,对于SQL Server里的统计信息是200步长的局限性(使用过滤统计信息可以超过这个步长,这在SQL Server 2008里就引入了)。

另外的局限性是统计信息的自动更新(Auto Update)机制:对于大于500行的表,如果500+20%的列值发生改变,统计信息才会更新。这就意味着,一旦表增长,你的统计信息的自动更新频率将越少(每次触发自动更新需要更多的记录修改)。

假设你有100000条记录的表,这个情况下,如果修改了20500(20%+500)的数据,统计信息才会自动更新。如果你有1000000条记录的表,你需要修改200500(20%+500)的数据,统计信息才会自动更新。这里用到的算法是指数的,不是线性的。在SQL Server里有2371的跟踪标志(trace flag)也会影响这个行为。

当你的执行计划里保航书签查找时,这个行为就会是巨大的问题。正如你知道的,基于当前的统计信息,如果查询的估计行数是非常少的,查询优化器才会选择书签查找运算符。如果你的统计信息过期,你的执行计划还是有效的话,SQL Server就会盲目重用缓存计划,你的页读取就会暴涨。我们来看看这个问题的具体例子。

失真的统计信息(Stale Statistics)

下面的脚本会创建有1500条记录的表,在column2列有平均的数据分布。另外我们在column2列上定义非聚集索引。

 1 CREATE TABLE Table1
 2 (
 3    Column1 INT IDENTITY,
 4    Column2 INT
 5 )
 6 GO
 7 
 8 -- Insert 1500 records into Table1
 9 SELECT TOP 1500 IDENTITY(INT, 1, 1) AS n INTO #Nums
10 FROM
11 master.dbo.syscolumns sc1
12 
13 INSERT INTO Table1 (Column2)
14 SELECT n FROM #nums
15 
16 DROP TABLE #nums
17 GO 
18 
19 CREATE NONCLUSTERED INDEX idx_Table1_Colum2 ON Table1(Column2)
20 GO

当你对表进行简单的SELECT * 查询时,你会得到带有书签查找运算符的执行计划:

1 SELECT * FROM dbo.Table1 WHERE Column2='9'

索引查找(Non Clustered)运算符可以看到,SQL Server估计行数是1(估计行数(Estimated Number of Rows)属性),实际上SQL Server也处理1条记录(实际行数(Actual Number of Rows)属性)。这就是说,我们这里用到的统计信息是准确的,查询本身产生3个逻辑读。

我们现在的表有1500条记录,因此当20% + 500条记录发生改变时,SQL Server会自动更新非聚集索引的统计信息。算一下,我们需要修改800条数据(1500 * 20% + 500)。

接下来我们对表做如下处理:我们对SQL Server做一点动作,只插入799条新记录。但799条记录的第2列值都是2。这就是说我们完全改变第2列的平均数据分布。统计信息会认为只有1条第2列值为2的记录返回,但实际上却有800条记录返回(1条已存在的,799条新插入的):

1 SELECT TOP 799 IDENTITY(INT, 1, 1) AS n INTO #Nums
2 FROM
3 master.dbo.syscolumns sc1
4 
5 INSERT INTO Table1 (Column2)
6 SELECT 2 FROM #nums
7 
8 DROP TABLE #nums
9 GO

现在我们来执行下列查询语句,找第2列值为2的记录,并打开执行计划显示和IO统计。 

1 SET STATISTICS IO ON
2 SELECT * FROM dbo.Table1 WHERE Column2 ='2'

 SQL Server重用了有书签查找的执行计划。这就是说执行计划里的书签查找执行了1500次——一次性对所有记录!这会耗费大量的逻辑读——SQL Server这里报告了806个页读取。

从图中可以看到,实际行数(Actual Number of Rows)现在已经远远超过了估计行数(Estimated Number of Rows)

SQL Server里失真的统计信息就会带来这样的问题。

小结

今天的性能调优培训我给你简单介绍了SQL Server里的统计信息。如你所见,失真的统计信息,对于缓存的,重用的执行计划会带来严重的性能问题。

我希望现在你已经能很好的理解SQL Server里的统计信息,当它们过期是,会给你的执行计划带来副作用。下周我会进一步讨论统计信息,还有在SQL Server内部它们是怎样的。请继续关注。

围观PPT:

0817_13统计信息.rar

<think>好的,我现在需要解决用户的问题:“Python求相对1个月的”。首先,我需要明确用户的需求到底是什么。可能用户是想用Python来计算一个月中的数,或者想知道某个日期在该月中的第几。不过“相对1个月的”这个表达有点模糊,需要进一步分析。 首先,我需要考虑不同的定义。通常,的计算方式有多种,比如: 1. 从该月的第一天开始计算,每七天,不管几开始。例如,如果一个月有31天,那么可能有四或五,取决于开始的那一天。 2. 按照国际标准ISO,其中每一开始,一年的第一是包含第一个星期四的那。但这可能跨年,所以可能需要调整。 3. 用户可能想按自然计算,例如每个一到日为一,但这样每个月的数可能不固定。 接下来,我需要考虑用户可能的实际应用场景。比如,生成某个月的历,或者统计某个月中的每数据。因此,用户可能需要确定给定日期属于该月的第几,或者计算该月总共有多少。 接下来,我需要思考如何在Python中实现这些计算。Python的标准库中有datetime和calendar模块,可能有用。比如,使用calendar.monthcalendar来生成该月的列表。不过需要注意的是,monthcalendar返回的列表可能包含前一个月或下一个月的日期,但会被表示为0。所以可能需要过滤这些0,只保留该月实际的日期,从而确定数。 例如,对于2023年10月,calendar.monthcalendar(2023, 10)的输出是: [[0, 0, 0, 0, 0, 0, 1], [2, 3, 4, 5, 6, 7, 8], [9, 10, 11, 12, 13, 14, 15], [16, 17, 18, 19, 20, 21, 22], [23, 24, 25, 26, 27, 28, 29], [30, 31, 0, 0, 0, 0, 0]] 虽然看起来有6行,但实际有效数可能为5,因为第一只有1天,最后一只有2天。但根据不同的业务需求,用户可能认为这是5还是6,或者需要排除不完全的。 另一种方法是计算该月第一个一(或其他起始日)所在的,然后每七天。但这种方法需要确定该月第一的开始日期。 或者,用户可能希望根据某一天(例如某个特定日期)来计算相对于该月开始的数。例如,2023-10-15是该月的第几,可能从该月的第一天开始算起,不管几。 比如,假设该月第一天是日,那么10月1日是第1,10月2日(一)到10月8日是第2,依此类推。或者,如果用户认为每一开始,那么可能需要调整起始日。 因此,可能需要用户明确的定义,但作为回答者,应该考虑到不同的情况,并给出相应的解决方案。 接下来,我需要编写代码示例,展示如何计算某个月的数或某日期的数。例如: 1. 使用calendar.monthcalendar获取列表,然后统计非全零的行数作为数。不过这可能包含部分,但用户可能接受。 2. 计算某日期是该月的第几,可以通过该日期的天数除以7,向上取整,例如(day -1)//7 +1。但这种方法不考虑几,只是按天数分段。 3. 使用isocalendar()[1]来获取ISO数,但需要注意跨月的情况,比如某个月的末尾可能属于下一年的第一。 例如,对于2023年10月1日,如果使用ISO,可能需要检查是否属于该月的数。这可能比较复杂,因为ISO可能跨年或跨月。 因此,可能需要不同的方法,根据用户的具体需求来选择。 现在,我需要将这些思路整理成一个清晰的步骤,并提供相应的Python代码示例,以帮助用户解决问题。可能需要先确定用户的需求,再给出不同的解决方案,并说明各自的适用场景。</think>要使用Python计算一个月内的数或确定某日期在该月中的数,需明确“”的定义。以下是不同场景下的解决方案: --- ### 一、按自然(每月1日为第1起始) **方法**:将天数除以7(向上取整),忽略星期几。 - **公式**: $$ \text{数} = \left\lceil \frac{\text{日期天数}}{7} \right\rceil $$ - **示例代码**: ```python from datetime import datetime def week_of_month(date_str): date = datetime.strptime(date_str, "%Y-%m-%d") day = date.day return (day - 1) // 7 + 1 # 测试:计算2023-10-15是该月的第几 print(week_of_month("2023-10-15")) # 输出:3 ``` --- ### 二、按实际日历(包含跨月) **方法**:使用`calendar.monthcalendar`生成列表,过滤非本月日期后统计数。 - **示例代码**: ```python import calendar def count_weeks(year, month): weeks = calendar.monthcalendar(year, month) # 过滤全为0的行(非本月日期) valid_weeks = [week for week in weeks if sum(week) > 0] return len(valid_weeks) # 测试:计算2023年10月的数 print(count_weeks(2023, 10)) # 输出:5 ``` --- ### 三、按ISO(跨年/跨月) **方法**:通过ISO数计算,需处理跨月情况。 - **示例代码**: ```python from datetime import datetime def iso_week_in_month(date_str): date = datetime.strptime(date_str, "%Y-%m-%d") # 获取该日期所在一 start_of_week = date - timedelta(days=date.weekday()) #
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值