SQL Server 还原
全备还原
还原代码: 修改相应的路径即可
USE [master]
RESTORE DATABASE [test] FROM DISK = N'D:\bak_db\quanbei_db\test_backtest_full_2020-02-12.bak'
WITH FILE = 1, NORECOVERY,NOUNLOAD,STATS = 5 --如果此处不是最后一步还原的话,使用NORECOVERY,如果此处是最后一步,就需要使用RECOVERY,否则会出问题
GO
等待执行完毕即可完成全备还原
差异备份还原
差异备还原代码:修改相对路径即可
USE [master]
RESTORE DATABASE [test] FROM DISK = N'D:\bak_db\differential_db\test_differential_backtest_differential_2020-02-12.bak'
WITH FILE = 1, NORECOVERY,NOUNLOAD,STATS = 5 --如果此处不是最后一步还原的话,使用NORECOVERY,如果此处是最后一步,就需要使用RECOVERY,否则会出问题
GO
等待执行完毕即可完成差异备份还原
日志备/尾日志还原
日志备份,尾日志备份的还原,都用该命令即可。记得修改相对应的路径
USE [master]
RESTORE LOG [test] FROM DISK = N'D:\bak_db\tlog_db2\tail_log.bak'
WITH FILE = 1, RECOVERY,NOUNLOAD,STATS = 5 --如果此处不是最后一步还原的话,使用NORECOVERY,如果此处是最后一步,就需要使用RECOVERY,否则会出问题
GO --- 如果上面报错,可以尝试将WITH后面的内容都删掉,只保留RECOVERY
等待执行完毕即可完成日志备份还原
事务日志还原
1、可视化操作:
a.打开数据库还原页面,检查源数据库及目标数据库是否正确,检查完整备份及日志备份是否完整,并点击“时间线”进入时间线选择
b.在弹出的时间线对话框中选择“特定日期和时间”,并将时间设置为误删之前时间点,如16:20
c.再次检查目标数据库,及备份集信息无误,点击“选项”进入选项卡,勾选“覆盖现有数据库”及“关闭现有连接”复选框,然后确定,直至还原成功
2、命令行操作
USE [master]
ALTER DATABASE [test] SET SINGLE_USER WITH ROLLBACK IMMEDIATE --- 修改数据库为单用户模式
-- 还原前进行尾日志备份
BACKUP LOG [test] TO DISK = N'D:\Program Files\Microsoft SQL Server\MSSQL14.MSSQLSERVER\MSSQL\Backup\test_LogBackup_2020-02-12_19-24-21.bak'
WITH NOFORMAT, NOINIT, NAME = N'test_LogBackup_2020-02-12_19-24-21', NOSKIP, NOREWIND, NOUNLOAD, NORECOVERY , STATS = 5
-- 还原全备备份
RESTORE DATABASE [test] FROM DISK = N'D:\bak_db\quanbei_db\test_backtest_full_2020-02-12.bak'
WITH FILE = 2, NORECOVERY, NOUNLOAD, REPLACE, STATS = 5
-- 还原事务日志备份,并设置还原到的时间节点
RESTORE LOG [test] FROM DISK = N'D:\bak_db\testback.trn'
WITH FILE = 1, NOUNLOAD, STATS = 5, STOPAT = N'2020-02-12T18:30:00'
ALTER DATABASE [test] SET MULTI_USER
GO
等待还原成功即可
备份还原中的问题
还原失败,需要某些权限
问题描述:
SQL Server 阻止了对组件 'xp_cmdshell' 的 过程'sys.xp_cmdshell' 的访问,因为此组件已作为此服务器安全配置的一部分而被关闭。系统管理员可以通过使用 sp_configure 启用 'xp_cmdshell'。有关启用 'xp_cmdshell' 的详细信息,请参阅 SQL Server 联机丛书中的 "外围应用配置器"。
EXEC sp_configure 'show advanced options', 1;
RECONFIGURE;
EXEC sp_configure 'xp_cmdshell', 1;
RECONFIGURE;
重命名sql Server数据库名称失败
问题描述:
消息 5030,级别 16,状态 2,第 1 行无法用排他锁锁定该数据库,以执行该操作。
视图模式解决:
1、在对象资源管理器中,连接到 SQL Server 数据库引擎实例,然后展开该实例。
2、右键单击要更改的数据库,再单击“属性”。
3、在“数据库属性”对话框中,单击“选项”页。
4、在“限制访问”选项中,选择“单用户”。
5、如果其他用户连接到数据库,将出现“打开的连接”消息。若要更改属性并关闭所有其他连接,请单击“是”。
6、重命名完了,再限制访问的属性改回原来的就可以啦
命令行模式解决
--1.执行SQL
ALTER DATABASE db_database SET SINGLE_USER WITH ROLLBACK IMMEDIATE
--修改为单用户模式
--2.然后关闭所有的查询窗口,重命名你的数据库名称
--3.执行SQL
ALTER DATABASE db_database SET MULTI_USER
--再修改为多用户模式
作业迁移
单个迁移
在ssms中,打开作业文件夹
右键要迁移的作业
编写作业脚本为-create到-新的查询窗口
将生成的脚本全部赋值下来
去目标数据库上面,新建查询窗口执行刚才生成的脚本
有可能迁移过后的脚本是禁用状态,
作业文件夹右键-管理计划-根据时间说明将对应的job开启。或者逐个点击开启
问题:
1、执行失败:可能原因是源服务器使用的windows账号登陆的,导致无法执行,这个时候需要将登陆账号("@owner_login_name")改为本地的账号
本地登陆名-》ssms中,安全性-登录名下所有启用的账户,此处尽量保证源db的用户和目标db的用户一致。
批量迁移
在ssms中,打开作业文件夹
选中作业文件夹,然后按F7,出现对象管理器详细信息界面
按着ctrl键,多选要迁移的作业job
右键-编写作业脚本为-create到-新的查询窗口
将生成的脚本全部复制,到迁移目标数据库上面,新建查询窗口,执行刚才生成的脚本,就可以完成批量迁移
有可能迁移过后的脚本是禁用状态,
作业文件夹右键-管理计划-根据时间说明将对应的job开启。或者逐个点击开启
问题:
1、执行失败:可能原因是源服务器使用的windows账号登陆的,导致无法执行,这个时候需要将登陆账号("@owner_login_name")改为本地的账号
本地登陆名-》ssms中,安全性-登录名下所有启用的账户,此处尽量保证源db的用户和目标db的用户一致。
登陆账号迁移
批量迁移脚本
---SQL 2005以上版本适用
--找到了解决办法.
--1.在A服务器上执行
USE master
GO
IF OBJECT_ID ('sp_hexadecimal') IS NOT NULL
DROP PROCEDURE sp_hexadecimal
GO
CREATE PROCEDURE sp_hexadecimal
@binvalue varbinary(256),
@hexvalue varchar (514) OUTPUT
AS
DECLARE @charvalue varchar (514)
DECLARE @i int
DECLARE @length int
DECLARE @hexstring char(16)
SELECT @charvalue = '0x'
SELECT @i = 1
SELECT @length = DATALENGTH (@binvalue)
SELECT @hexstring = '0123456789ABCDEF'
WHILE (@i <= @length)
BEGIN
DECLARE @tempint int
DECLARE @firstint int
DECLARE @secondint int
SELECT @tempint = CONVERT(int, SUBSTRING(@binvalue,@i,1))
SELECT @firstint = FLOOR(@tempint/16)
SELECT @secondint = @tempint - (@firstint*16)
SELECT @charvalue = @charvalue +
SUBSTRING(@hexstring, @firstint+1, 1) +
SUBSTRING(@hexstring, @secondint+1, 1)
SELECT @i = @i + 1
END
SELECT @hexvalue = @charvalue
GO
IF OBJECT_ID ('sp_help_revlogin') IS NOT NULL
DROP PROCEDURE sp_help_revlogin
GO
CREATE PROCEDURE sp_help_revlogin @login_name sysname = NULL AS
DECLARE @name sysname
DECLARE @type varchar (1)
DECLARE @hasaccess int
DECLARE @denylogin int
DECLARE @is_disabled int
DECLARE @PWD_varbinary varbinary (256)
DECLARE @PWD_string varchar (514)
DECLARE @SID_varbinary varbinary (85)
DECLARE @SID_string varchar (514)
DECLARE @tmpstr varchar (1024)
DECLARE @is_policy_checked varchar (3)
DECLARE @is_expiration_checked varchar (3)
DECLARE @defaultdb sysname
IF (@login_name IS NULL)
DECLARE login_curs CURSOR FOR
SELECT p.sid, p.name, p.type, p.is_disabled, p.default_database_name, l.hasaccess, l.denylogin FROM
sys.server_principals p LEFT JOIN sys.syslogins l
ON ( l.name = p.name ) WHERE p.type IN ( 'S', 'G', 'U' ) AND p.name <> 'sa'
ELSE
DECLARE login_curs CURSOR FOR
SELECT p.sid, p.name, p.type, p.is_disabled, p.default_database_name, l.hasaccess, l.denylogin FROM
sys.server_principals p LEFT JOIN sys.syslogins l
ON ( l.name = p.name ) WHERE p.type IN ( 'S', 'G', 'U' ) AND p.name = @login_name
OPEN login_curs
FETCH NEXT FROM login_curs INTO @SID_varbinary, @name, @type, @is_disabled, @defaultdb, @hasaccess, @denylogin
IF (@@fetch_status = -1)
BEGIN
PRINT 'No login(s) found.'
CLOSE login_curs
DEALLOCATE login_curs
RETURN -1
END
SET @tmpstr = '/* sp_help_revlogin script '
PRINT @tmpstr
SET @tmpstr = '** Generated ' + CONVERT (varchar, GETDATE()) + ' on ' + @@SERVERNAME + ' */'
PRINT @tmpstr
PRINT ''
WHILE (@@fetch_status <> -1)
BEGIN
IF (@@fetch_status <> -2)
BEGIN
PRINT ''
SET @tmpstr = '-- Login: ' + @name
PRINT @tmpstr
IF (@type IN ( 'G', 'U'))
BEGIN -- NT authenticated account/group
SET @tmpstr = 'CREATE LOGIN ' + QUOTENAME( @name ) + ' FROM WINDOWS WITH DEFAULT_DATABASE = [' + @defaultdb + ']'
END
ELSE BEGIN -- SQL Server authentication
-- obtain password and sid
SET @PWD_varbinary = CAST( LOGINPROPERTY( @name, 'PasswordHash' ) AS varbinary (256) )
EXEC sp_hexadecimal @PWD_varbinary, @PWD_string OUT
EXEC sp_hexadecimal @SID_varbinary,@SID_string OUT
-- obtain password policy state
SELECT @is_policy_checked = CASE is_policy_checked WHEN 1 THEN 'ON' WHEN 0 THEN 'OFF' ELSE NULL END FROM sys.sql_logins WHERE name = @name
SELECT @is_expiration_checked = CASE is_expiration_checked WHEN 1 THEN 'ON' WHEN 0 THEN 'OFF' ELSE NULL END FROM sys.sql_logins WHERE name = @name
SET @tmpstr = 'CREATE LOGIN ' + QUOTENAME( @name ) + ' WITH PASSWORD = ' + @PWD_string + ' HASHED, SID = ' + @SID_string + ', DEFAULT_DATABASE = [' + @defaultdb + ']'
IF ( @is_policy_checked IS NOT NULL )
BEGIN
SET @tmpstr = @tmpstr + ', CHECK_POLICY = ' + @is_policy_checked
END
IF ( @is_expiration_checked IS NOT NULL )
BEGIN
SET @tmpstr = @tmpstr + ', CHECK_EXPIRATION = ' + @is_expiration_checked
END
END
IF (@denylogin = 1)
BEGIN -- login is denied access
SET @tmpstr = @tmpstr + '; DENY CONNECT SQL TO ' + QUOTENAME( @name )
END
ELSE IF (@hasaccess = 0)
BEGIN -- login exists but does not have access
SET @tmpstr = @tmpstr + '; REVOKE CONNECT SQL TO ' + QUOTENAME( @name )
END
IF (@is_disabled = 1)
BEGIN -- login is disabled
SET @tmpstr = @tmpstr + '; ALTER LOGIN ' + QUOTENAME( @name ) + ' DISABLE'
END
PRINT @tmpstr
END
FETCH NEXT FROM login_curs INTO @SID_varbinary, @name, @type, @is_disabled, @defaultdb, @hasaccess, @denylogin
END
CLOSE login_curs
DEALLOCATE login_curs
RETURN 0
GO
批量迁移执行方法
exec master..sp_help_revlogin
执行上面的语句,可以生成创建账号登录名的脚本命令
执行结果: /* sp_help_revlogin script ** Generated 02 13 2020 10:42AM on iZ1kh8t10gh906Z */ -- Login: ##MS_PolicyEventProcessingLogin## CREATE LOGIN [##MS_PolicyEventProcessingLogin##] WITH PASSWORD = 0x020064000B95A868F1C96CC6F7B3C26CE4ADF0533DD589C4DC0CB8355ED932EB094F0E343441373E43C30AD02CC138846D30122842BEA6487E11EE0FA8DF48A9E3E628073EEA HASHED, SID = 0xB358F79FA0D32A4E9087D7897F494F6A, DEFAULT_DATABASE = [master], CHECK_POLICY = ON, CHECK_EXPIRATION = OFF; ALTER LOGIN [##MS_PolicyEventProcessingLogin##] DISABLE -- Login: ##MS_PolicyTsqlExecutionLogin## CREATE LOGIN [##MS_PolicyTsqlExecutionLogin##] WITH PASSWORD = 0x0200BED6D4630027FC65C53E58F4F75AED5A81674024B15A88268D8E2927EF350C25FA7132BDF4524207AC29AB469B61AEE3B7AFA4075A3D41816266A670CCC8C32D9968EBB6 HASHED, SID = 0xCE62FF92465C5E4E90E9AC73CD01CA8D, DEFAULT_DATABASE = [master], CHECK_POLICY = ON, CHECK_EXPIRATION = OFF; ALTER LOGIN [##MS_PolicyTsqlExecutionLogin##] DISABLE -- Login: IZ1KH8T10GH906Z\Administrator CREATE LOGIN [IZ1KH8T10GH906Z\Administrator] FROM WINDOWS WITH DEFAULT_DATABASE = [master] -- Login: NT SERVICE\SQLWriter CREATE LOGIN [NT SERVICE\SQLWriter] FROM WINDOWS WITH DEFAULT_DATABASE = [master] -- Login: NT SERVICE\Winmgmt CREATE LOGIN [NT SERVICE\Winmgmt] FROM WINDOWS WITH DEFAULT_DATABASE = [master] -- Login: NT Service\MSSQLSERVER CREATE LOGIN [NT Service\MSSQLSERVER] FROM WINDOWS WITH DEFAULT_DATABASE = [master] -- Login: NT AUTHORITY\SYSTEM CREATE LOGIN [NT AUTHORITY\SYSTEM] FROM WINDOWS WITH DEFAULT_DATABASE = [master] -- Login: NT SERVICE\SQLSERVERAGENT CREATE LOGIN [NT SERVICE\SQLSERVERAGENT] FROM WINDOWS WITH DEFAULT_DATABASE = [master] -- Login: NT SERVICE\SQLTELEMETRY CREATE LOGIN [NT SERVICE\SQLTELEMETRY] FROM WINDOWS WITH DEFAULT_DATABASE = [master] -- Login: test_login CREATE LOGIN [test_login] WITH PASSWORD = 0x02007CF5FE5C7B585BD57A1C4E7D4A482B2638B0FAF49D7D201696A08C83B184867E3A1DE8653642289A5A41E6AD44182D361C97C0C6C9804495273F517CE672ED5BDD960F17 HASHED, SID = 0xB2C428D6E825844DA65209F5B1E52B1D, DEFAULT_DATABASE = [master], CHECK_POLICY = ON, CHECK_EXPIRATION = ON 完成时间: 2020-02-13T10:42:10.9315302+08:00
注意,一般情况下,就迁移自己创建的即可,其他的上面登录名可以不用迁移。NT开头的,和windows登陆的两种,可以忽略,这个这次执行命令,可以看到只有一个自建登录名,test_login。 这个脚本无法生成sa,需要自己创建修改密码等
总结
使用这个办法有2个好处。
1。可以批量同步所有需要的登录账号 2.由于产生的SID是相同的,不会有孤立账号的问题。在ALWAY ON环境下,我们就需要采用这种方式。不然没有ALWAYS ON 切换后都需要手动处理 孤立账号的问题
批量迁移连接服务器
然后把生成的脚本,COPY到新的服务器上面执行就可以了。在建立完成后一定要测试链接服务器是不是可用
总结
批量迁移链接服务器的方法和批量迁移定时作业的方法机会是一样的。
记录数据库的DDL
使用脚本记录下函数,存储过程,触发器,表结构的修改
-- Table to store the data
CREATE TABLE DDLLog(
id INT NOT NULL IDENTITY(1,1),
event_type sysname,
object_id int,
object_name sysname,
change_date datetime,
changed_by sysname
);
GO
CREATE TRIGGER tr_DDLLog
ON DATABASE
FOR DDL_PROCEDURE_EVENTS,
DDL_FUNCTION_EVENTS,
DDL_VIEW_EVENTS,
DDL_TRIGGER_EVENTS,
DDL_TABLE_EVENTS
AS
BEGIN
INSERT INTO DDLLog(event_type, object_id, object_name, change_date, changed_by)
VALUES (EVENTDATA().value('(/EVENT_INSTANCE/EventType)[1]', 'NVARCHAR(255)'),
EVENTDATA().value('(/EVENT_INSTANCE/ObjectId)[1]', 'INT'),
EVENTDATA().value('(/EVENT_INSTANCE/ObjectName)[1]', 'NVARCHAR(255)'),
getdate(),
ORIGINAL_LOGIN()
);
END
测试
--创建存储过程
CREATE PROCEDURE ChangeTest AS
PRINT '1';
GO
--修改存储过程
ALTER PROCEDURE ChangeTest AS
PRINT '1';
GO
--删除存储过程
DROP PROCEDURE ChangeTest;
GO
结果
##总结
当下次发生问题时,可以自己心中有数。
数据库重命名
##问题描述:
在数据库重命名的时候,平时最经常使用的就是sp_renamedb ,或者是在SSMS工具中右键进行重命名。这样操作简单,快捷。但是有一个问题就是对应的mdf,ldf文件名字不会跟着改变.而且数据库的逻辑文件名也不会跟着改变。
最佳实践
- 修改数据库的逻辑文件名字
ALTER DATABASE Mydb SET SINGLE_USER WITH ROLLBACK IMMEDIATE
ALTER DATABASE [Mydb] MODIFY FILE (NAME=N'Mydb', NEWNAME=N'MydbNew')
GO
ALTER DATABASE [Mydb] MODIFY FILE (NAME=N'Mydb_log', NEWNAME=N'MydbNewlog')
GO
- 修改物理文件名字
- 分离数据库
USE [master]
GO
EXEC master.dbo.sp_detach_db @dbname = N'Mydb'
GO
- 修改物理文件名字
找到db对应的ldf和mdf的路径,进行重命名
- 附加数据库
USE [master]
GO
CREATE DATABASE MydbNew ON
( FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\MydbNew.mdf' ),
( FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\MydbNew_log.ldf' )
FOR ATTACH
GO
- 设置为多用户
ALTER DATABASE MydbNew SET MULTI_USER
- 检查
--查看数据库的文件名: 修改最后的db名字
SELECT
name AS [Logical Name],
physical_name AS [DB File Path],
type_desc AS [File Type],
state_desc AS [State]
FROM sys.master_files
WHERE database_id = DB_ID(N'MydbNew')
总结
修改数据库名字是一个很常见的操作,直接改名很容易,但是可能会以后留下隐患。
慢sql优化
根据执行计划进行优化比对 测试表中,有两个字段,id和name,id为主键,有1200条数据 执行一条sql
select name,id from tb_test where name='小鱼儿'
通过执行计划可以看到,读取的行数是1201行,Ordered,False。 读取了1201行,说明是全表扫描 Ordered false 说明没有使用到主键。
select name,id from tb_test where id =14192
这条sql读取的行数是1,只读取了一条。 ordered是true,说明使用到了索引
##总结
两种计算方式 seek(索引),scan(扫描)
使用Scan的方式,SQL Server 会去读取Orders表中的每一行数据,读取的时候评估是否满足谓词 “where order=2”。如果满足(数据行符合条件),则返回该行。这个例子里,我们将这个谓词称作“residual predicate”。为了得到最优的性能,SQL会尽可能地在扫描中使用“residual predicate”。但如果residual predicate的开销过于昂贵,SQL Server可能会使用单独的“filter iterator”. “residual predicate”以where关键字的形式出现在文本格式的plan中。对XML格式的plan,则是 标记的形式。
继续以上面的查询为例子,如果在orderkey列上有一个索引,那么seek可能会是一个好的选择。使用seek的访问方式,SQL Server会使用索引直接导向到满足谓词条件的数据行。 这个例子里,我们将这个谓词称为“seek predicate”。 大多数情况下,SQL Server不必将“seek predicate”重新评估为“residual predicate”。 索引会保证“seek”只返回符合条件的数据行。“seek predicate”以seek关键字的形式出现在文本格式的plan中。 对于xml 格式的plan,则以 标记出现。
通过图片可以直观的看到,有索引的执行比没索引的执行,从读取行数,cpu,io等开销都要优秀很多。
关于scan和seek的问题
事务
在执行sql语句的时候
begin tran --开启事务
--- 执行sql语句
-- 观察执行结果,判断是否有问题
--如果有问题 Rollback 回滚
--如果没问题 commit tran 提交确定
#表瘦身
表瘦身
后续补充!
切表
思路
1、新建一个表 2、将表里面的历史数据(看哪部分少,就转移哪部分)转移到新表中 3、修改db的名字(在一个事务中)
将历史数据缓慢移动到历史表中
--需求,将老表中的数据,只保留最新300条,其他的都转移到历史表中
-- 当前id 最大id
-- 查询出最大id并保存
-- 循环查询区间数据,保存到新表中
declare @maxId int
declare @id int
declare @num int = 0
declare @exNum int = 0
begin
-- 查询出最大转移id
set @maxId = (select top 1 id from (
select top 300 * from tb_test with(nolock) order by id desc
) b order by id)
-- 初始id为0
set @id = 0
while (select count(*) from tb_test with(nolock) where id>@id and id <@maxId)>0
begin try
--开启事务
begin tran
--插入数据
insert into tb_test_back(id,name) select top 10 * from tb_test with(nolock) where id>@id and id<@maxId
--更新当前id
set @id = (select top 1 id from (
select top 10 * from tb_test with(nolock) where id>@id and id<@maxId order by id
) b order by b.id desc)
-- 初始id为0
--删除老表数据
print('删除@id是:')
print(@id)
delete from tb_test where id<@id
--select * from tb_test where id<10009
--提交
commit tran
set @num = @num+1
end try
--异常捕捉
begin catch
ROLLBACK TRAN
set @exNum = @exNum+1
end catch
print(@num)
print(@exNum)
end
将少量最新数据移动到新表中,然后对两个表做重命名操作
select * from [dbo].[sql_profiler]
select [RowNumber] from sql_profiler
select name,id from tb_test where name='小鱼儿'
select name,id from tb_test where id =14192
insert into tb_test(name) values('小鱼儿')
begin tran
update tb_test set name = '小鱼儿2' where id = 14192
delete from tb_test where id = 14192
Rollback
commit tran
begin tran
select top 300 * from tb_test with(nolock) order by id desc
select * into tb_test_back from tb_test where id<10901
set IDENTITY_INSERT #tb_test on
insert into tb_test(name) select top 902 name from tb_testback where id<10901
--查询表中数据量
select count(*) from tb_test with(nolock)
--查询备份表中的数据量
select count(*) from tb_testback with(nolock)
--清空表
truncate table tb_test_back
delete from tb_test where id = 10900
declare @a int
set @a = 1190
while (select count(*) from tb_test)>@a
begin
print('ok')
print(@a)
set @a = @a+1
end
declare @a int
set @a = 1190
if (select count(*) from tb_test)>@a
begin
print('ok')
print(@a)
end
else
begin
print('no')
print(@a)
end