0
http://www.sqlskills.com/blogs/kimberly/exec-and-sp_executesql-how-are-they-different/
http://www.sqlskills.com/blogs/kimberly/high-performance-procedures/
Posted on
Monday, January 04, 2016
by
醉·醉·鱼
and labeled under
sql
引用: http://www.brentozar.com/archive/2013/12/recompile-hints-and-execution-plan-caching/http://www.sqlskills.com/blogs/kimberly/exec-and-sp_executesql-how-are-they-different/
http://www.sqlskills.com/blogs/kimberly/high-performance-procedures/
Kimberly Tripp 看上去建议使用Dynamic query来避免parameter sniffing的问题。这里来一个简化版了。
第一次执行SP的时候,query plan会被cache起来。第二次执行的时候,会被用到。很显然,第二次会遇到parameter sniffing问题。
ALTER PROCEDURE Get_OrderID_OrderQty
@ProductID INT,
@OrderId INT = NULL
AS
SELECT SalesOrderDetailID, OrderQty
FROM Sales.SalesOrderDetail
WHERE ProductID = @ProductID
AND (SalesOrderID = @OrderId OR @OrderId IS NULL)
GO
SET STATISTICS IO ON
EXEC Get_OrderID_OrderQty @ProductID=870
EXEC Get_OrderID_OrderQty @ProductID=870, @OrderId= 51108
SET STATISTICS IO OFF
改成Dynamic query的方式,就好了。


这样就好了?那我们就too young too simple了。有一个明显的问题,就是tipping point。如果这个时候执行下面的query,你就会得到的一个糟糕的结果。实际上,IO由1246变成了14379,这是非常非常的糟糕的。最好参数里面能够有high selective的,否则还是得老老实实加上OPTION(RECOMPILE)。
ALTER PROCEDURE Get_OrderID_OrderQty
@ProductID INT,
@OrderId INT = NULL
AS
DECLARE @sql NVARCHAR(MAX) = N'';
SET @sql = 'SELECT SalesOrderDetailID, OrderQty
FROM Sales.SalesOrderDetail
WHERE ProductID = @ProductID '
IF @OrderId IS NOT NULL
SET @sql = @sql + 'AND SalesOrderID = @OrderId'
EXEC sys.sp_executesql @sql
, N'@ProductID INT, @OrderId INT'
, @ProductID, @OrderId
GO
SET STATISTICS IO ON
EXEC Get_OrderID_OrderQty @ProductID=897
EXEC Get_OrderID_OrderQty @ProductID=897, @OrderId= 51108
SET STATISTICS IO OFF


这样就好了?那我们就too young too simple了。有一个明显的问题,就是tipping point。如果这个时候执行下面的query,你就会得到的一个糟糕的结果。实际上,IO由1246变成了14379,这是非常非常的糟糕的。最好参数里面能够有high selective的,否则还是得老老实实加上OPTION(RECOMPILE)。
SET STATISTICS IO ON
EXEC Get_OrderID_OrderQty @ProductID=870
SELECT SalesOrderDetailID, OrderQty, SalesOrderID
FROM Sales.SalesOrderDetail
WHERE ProductID = 870
SET STATISTICS IO OFF
0
http://www.brentozar.com/archive/2013/12/recompile-hints-and-execution-plan-caching/
http://www.sqlpass.org/summit/2014/PASStv.aspx?watch=p-6ii2NiUI0


事实上,这里涉及到SQL SERVER的tipping point了。所以,在这个例子里面,我们只能够加上OPTION(RECOMPILE)了。至少我现在还没有想出什么好方法。
Posted on
Monday, January 04, 2016
by
醉·醉·鱼
and labeled under
sql
引用: http://www.brentozar.com/archive/2013/06/the-elephant-and-the-mouse-or-parameter-sniffing-in-sql-server/http://www.brentozar.com/archive/2013/12/recompile-hints-and-execution-plan-caching/
http://www.sqlpass.org/summit/2014/PASStv.aspx?watch=p-6ii2NiUI0
- SP_RECOMPILE 在不知道怎么改的时候,一般的临时方案就是sp_recompile.
EXEC sys.sp_recompile 'Get_OrderID_OrderQty' GO
- WITH RECOMPILE 能够工作,但是这样做会没有cached query plan,也没有DMV data。
ALTER PROCEDURE Get_OrderID_OrderQty @ProductID INT WITH RECOMPILE AS SELECT SalesOrderDetailID, OrderQty FROM Sales.SalesOrderDetail WHERE ProductID = @ProductID;
- OPTION RECOMPILE 比上面稍微好点,能够cached 最后一次执行的query plan,DMV里面也能够看到数据了。
ALTER PROCEDURE Get_OrderID_OrderQty @ProductID INT AS SELECT SalesOrderDetailID, OrderQty FROM Sales.SalesOrderDetail WHERE ProductID = @ProductID OPTION(RECOMPILE);
- 核武器 - DBCC FREEPROCCACHE 世界一下清净了,所有的都需要重新compile和cache。但这只是短暂的,等下一次糟糕的query plan被cache起来以后,问题还是会出现。
- Dynamic query?这个看上去是个不错的选择。但实际上我还是发现,我需要加上OPTION(RECOMPILE),否则,第一次执行的query plan会被cache起来被第二次执行用掉,问题依旧。而加上以后,其实就是第三个方案一样了。
ALTER PROCEDURE Get_OrderID_OrderQty @ProductID INT AS DECLARE @sql NVARCHAR(MAX) = N'' SET @sql = 'SELECT SalesOrderDetailID, OrderQty FROM Sales.SalesOrderDetail WHERE ProductID = @ProductID OPTION(RECOMPILE)' EXEC sys.sp_executesql @sql, N'@ProductId INT', @ProductID GO
SET STATISTICS IO ON
EXEC Get_OrderID_OrderQty @ProductID=870
EXEC Get_OrderID_OrderQty @ProductID=897
SET STATISTICS IO OFF


事实上,这里涉及到SQL SERVER的tipping point了。所以,在这个例子里面,我们只能够加上OPTION(RECOMPILE)了。至少我现在还没有想出什么好方法。
0
引用 http://www.brentozar.com/archive/2013/06/the-elephant-and-the-mouse-or-parameter-sniffing-in-sql-server/
执行下面的query。第一次执行的query plan会被cache起来,并被第二个query引用。问题也就出在第二次query上面。可以从IO 和执行计划上面看出,SQL SERVER使用了一个不是很好,有时候可能会使很糟糕的执行计划。IO从之前的10跑到了1246,看执行计划也会发现,预估的是返回4688条记录,实际上只有2条记录,相差太大了。
Posted on
Monday, January 04, 2016
by
醉·醉·鱼
and labeled under
sql
引用 http://www.brentozar.com/archive/2013/06/the-elephant-and-the-mouse-or-parameter-sniffing-in-sql-server/
什么是parameter sniffing
来看个例子。
CREATE PROCEDURE Get_OrderID_OrderQty
@ProductID INT
AS
SELECT SalesOrderDetailID, OrderQty
FROM Sales.SalesOrderDetail
WHERE ProductID = @ProductID;
执行下面的query。第一次执行的query plan会被cache起来,并被第二个query引用。问题也就出在第二次query上面。可以从IO 和执行计划上面看出,SQL SERVER使用了一个不是很好,有时候可能会使很糟糕的执行计划。IO从之前的10跑到了1246,看执行计划也会发现,预估的是返回4688条记录,实际上只有2条记录,相差太大了。
0
https://www.youtube.com/watch?list=PLoGAcXKPcRvanix7u9eqg_qt1tp849rX3&v=p-6ii2NiUI0
MSDN上面有这么一段关于plan cache的文章。大致就是说
Posted on
Tuesday, December 29, 2015
by
醉·醉·鱼
and labeled under
sql
引用: https://technet.microsoft.com/en-us/library/ms181055(v=sql.105).aspxhttps://www.youtube.com/watch?list=PLoGAcXKPcRvanix7u9eqg_qt1tp849rX3&v=p-6ii2NiUI0
MSDN上面有这么一段关于plan cache的文章。大致就是说
- 当没有cached query plan 的时候,执行query就会创建一个query plan并被cache起来。
- 下面这些操作可以导致cached plan被清掉
- SERVER RESTART/DATABASE LEVEL CHANGES/CONFIGURATION CHANGES
- DBCC FREEPROCCACHE/DBCC FREESYSTEMCACHE/SP_RECOMPILE
- DROP & RECREATE SP
- Object changes,包括view,table,statistics
- 长时间没用
MSDN提到,SQL SERVER采用了基于COST的机制去清理query plan。如果当前没有内存压力,SQL SERVER 不会去清query plan。如果有内存压力,SQL SERVER回去验证每个query plan,并且降低他们的currect cost,直至0。一旦当它变成0并且内存压力来的时候,每次验证的同时就会清掉这个plan。如果在这之前这个query plan被用掉,那current cost又会被reset成最原来的cost,即不会被清掉。
0
百思不得其解。在遇到N次这事之后,我就去看log,看邮件,看自己job的日志。突然发现一件事,不知道什么时候,cron的PID 会变成一个很小的number,然后一路往上升,直到下次又变成一个很小的number。一旦PID变成很小的number,第一个被启动的job就不会正常工作。
一般来说,cron的job被启动,会在cron.log里面有记录,在系统邮件(/var/mail)里面也有记录。如下图,在10月7日早上10点,有一个脚本被启动了,但是在系统邮件里面却没法找到对应的记录。在这个job之前和之后的job在系统邮件和cron.log里面都有记录相对应,唯独这条没有。同时,也可以看到,大概4个小时前,PID number由64348降成352。
我又找了几个例子,都能够对应我上面的结论:“一旦PID变成很小的number,第一个被启动的job就不会正常工作。”
现在我只能够在我需要跑的job前一分钟再添加一个job A,啥事都不做,只是单独占个坑,这样就算倒霉也就是这个job A没法正常启动。
这都是啥奇葩的事情。
Posted on
Monday, October 12, 2015
by
醉·醉·鱼
and labeled under
cron
用ruby写了一个定期发邮件的脚本,然后又写了个sh去调这个脚本,在cron里面添加一个job定期调用这个sh文件,然后他就能够正常工作了。但是最近突然发现有时候他居然不能够发邮件了。我以为又是gmail被墙了,但是我上次已经改过host了,应该不会有问题了。而且还加了30分钟内retry的机制。64.233.161.108 smtp.gmail.com
百思不得其解。在遇到N次这事之后,我就去看log,看邮件,看自己job的日志。突然发现一件事,不知道什么时候,cron的PID 会变成一个很小的number,然后一路往上升,直到下次又变成一个很小的number。一旦PID变成很小的number,第一个被启动的job就不会正常工作。
一般来说,cron的job被启动,会在cron.log里面有记录,在系统邮件(/var/mail)里面也有记录。如下图,在10月7日早上10点,有一个脚本被启动了,但是在系统邮件里面却没法找到对应的记录。在这个job之前和之后的job在系统邮件和cron.log里面都有记录相对应,唯独这条没有。同时,也可以看到,大概4个小时前,PID number由64348降成352。
我又找了几个例子,都能够对应我上面的结论:“一旦PID变成很小的number,第一个被启动的job就不会正常工作。”
现在我只能够在我需要跑的job前一分钟再添加一个job A,啥事都不做,只是单独占个坑,这样就算倒霉也就是这个job A没法正常启动。
这都是啥奇葩的事情。
0
其实,网上已经有很多计算器了,比如新浪的计算器就算还能够过去,只是有两点
- 计算不够精确。其实这个不能够怪新浪的计算器。实际上每个银行在计算每年1月份利率调整的方法各不一样。比如这篇博客里面解释了工商银行的计算方式,而我按照他的计算方式去算结果总是会有一些偏差。
- 局限性。他只能够计算到3个利率变化的情况,一般5年以上的贷款利率变化很有可能超过3个。
我按照我自己的案例,写了以下脚本,去看农行是如何计算1月份的本息和的。结果发现,农行在计算一月份利息的时候是按照通用分段计息的方法,但是本金却是继续延续上一轮利率计算的本金。比如,12月份的剩余本金是10000块,那么1月份的本金是按照上一年的利率算,利息又是分段计算,然后加起来算出本息和。此后2月份就正常了。
反正每年1月份都是算的31天的利率,基本上1月份都要比平时还得多。
require 'date'
# 贷款总额
total_loan_money = 50 * 10000
# 贷款年限
total_loan_month = 20 * 12
# 初始利率
initial_interest_rate = 4.7
# 第一次还贷时间
initial_pay_dt = '2011-05-08'
# 利率调整时间表
interest_rate_changes = []
interest_rate_changes << [initial_pay_dt, initial_interest_rate]
interest_rate_changes << ['2012-01-01', 4.9]
interest_rate_changes << ['2013-01-01', 4.5]
interest_rate_changes << ['2015-01-01', 4.25]
interest_rate_changes << ['2016-01-01', 3.5]
p interest_rate_changes
def averageInterest(n, money, rate, ini_pay_dt)
list = []
money_to_pay_per_month = (money * rate * (1 + rate) ** n /((1 + rate) ** n - 1)).round(2)
n.times do |i|
remaining_load_money = (i==0) ? money : list[i-1][4]
interest_to_pay = (remaining_load_money * rate).round(2)
load_money_to_pay = (money_to_pay_per_month - interest_to_pay).round(2)
# [date, 本期利息,本期本金,本期本息,剩余本金]
list << [ini_pay_dt.strftime('%Y-%m-%d'), interest_to_pay, load_money_to_pay,
money_to_pay_per_month, (remaining_load_money - load_money_to_pay).round(2)]
ini_pay_dt = ini_pay_dt.next_month
end
list
end
def averageInterestAdjust(list, date, total_loan_month, rate1, rate2)
tmp_list = []
last_list = list[-2]
money = last_list[4]
rate1_per_day = rate1 / 30
rate2_per_day = rate2 / 30
ini_pay_dt = Date.parse(last_list[0])
rate1_days = date - ini_pay_dt
rate2_days = ini_pay_dt.next_month - date
# 本月利息
interest_to_pay = (money * (rate1_per_day * rate1_days +
rate2_per_day * rate2_days)).round(2)
# 本月应还金额
# 农业银行特色 应还本金还是按照上一年的利率计算的,但是利息是分段计算的
money_to_pay = (interest_to_pay + list[-1][2]).round(2)
tmp_list = [ini_pay_dt.strftime('%Y-%m-%d'), interest_to_pay,
(money_to_pay - interest_to_pay), money_to_pay,
money-(money_to_pay - interest_to_pay)]
list[-1][1] = tmp_list[1].round(2)
list[-1][2] = tmp_list[2].round(2)
list[-1][3] = tmp_list[3].round(2)
list[-1][4] = tmp_list[4].round(2)
list
end
list = []
i = 0
ini_pay_dt = Date.parse(initial_pay_dt)
interest_rate_changes.each do |t|
date = Date.parse(t[0])
if ini_pay_dt == date
i += 1
next
else
diff = (date.year - ini_pay_dt.year) * 12 + (date.month - ini_pay_dt.month)
end
list += averageInterest(total_loan_month, total_loan_money,
interest_rate_changes[i-1][1]/100.0/12.0, ini_pay_dt).slice(0, diff+1)
total_loan_month -= (diff)
# 每年1月1日利率调整,按日计息
list = averageInterestAdjust(list, date, total_loan_month,
interest_rate_changes[i-1][1]/100.0/12.0, interest_rate_changes[i][1]/100.0/12.0)
total_loan_month -= 1
ini_pay_dt = Date.parse(list[-1][0]).next_month
total_loan_money = list[-1][4]
i += 1
end
list += averageInterest(total_loan_month, total_loan_money,
interest_rate_changes[-1][1]/100.0/12.0, ini_pay_dt)
total_interest = 0
list.each do |t|
p t
total_interest += t[1]
end
puts total_interest
0
最近在玩ActiveRecord和Sql server,发现ActiveRecord居然支持preparedstatement了。但有些功能还是不能支持,比如NOT EXISTS和CROSS APPLY。
比如,下面这段SQL就没有办法转换成为ActiveRecord
只是有些接近的方法
生成的SQL实际上是
用到了LEFT JOIN,而不是EXISTS。不过,还好是prepared statement。
或者直接用SQL
实际上面生成的SQL就不是prepared过的了。这样就不好了,就不好的利用cache plan了。比起LEFT JOIN 引起的side effect,我更加倾向用LEFT JOIN方式,至少能够cache plan,不用每次compile。
比如,下面这段SQL就没有办法转换成为ActiveRecord
只是有些接近的方法
生成的SQL实际上是
用到了LEFT JOIN,而不是EXISTS。不过,还好是prepared statement。
或者直接用SQL
实际上面生成的SQL就不是prepared过的了。这样就不好了,就不好的利用cache plan了。比起LEFT JOIN 引起的side effect,我更加倾向用LEFT JOIN方式,至少能够cache plan,不用每次compile。