sql server 性能
介绍 (Introduction)
In this article, we will verify if the following statements are true or they are just myths:
在本文中,我们将验证以下陈述是正确的还是仅仅是神话:
- Truncate is faster than a delete and it consumes less space in the logs 截断比删除快,并且在日志中占用的空间更少
- Do not use cursors to Create or drop multiple objects because it is very slow 不要使用游标创建或删除多个对象,因为它非常慢
- Do not use cursors to convert or process row values because it is very slow 不要使用游标转换或处理行值,因为它非常慢
入门 (Getting started)
- Truncate is faster than a delete and it consumes less space in the logs 截断比删除快,并且在日志中占用的空间更少
To delete all rows in a table, the best option is to use a truncate statement. This option is faster and consumes fewer system and log resources. But is it true?
要删除表中的所有行,最好的选择是使用截断语句。 此选项更快,并且消耗更少的系统和日志资源。 但这是真的吗?
We will test if that is true by creating a table with a million rows in two different databases. We will test the delete statement in one and in another; we will test the truncate statement.
我们将通过在两个不同的数据库中创建具有一百万行的表来测试这是否成立。 我们将在另一个测试删除语句; 我们将测试截断语句。
We will use the simple recovery model in the database to reduce the number of entries in the log.
我们将使用数据库中的简单恢复模型来减少日志中的条目数。
We will first create 2 databases:
我们将首先创建2个数据库:
create database deleteDemo
创建数据库deleteDemo
create database truncateDemo
创建数据库truncateDemo
These databases will be used to compare the results of the delete and truncate statements:
这些数据库将用于比较delete和truncate语句的结果:
Figure 0. 2 databases created 图0. 2个数据库已创建 The initial size is 16 MB for both databases (this value depends on the SQL Server version):
两个数据库的初始大小均为16 MB(此值取决于SQL Server版本):
Figure 1. Database size 图1.数据库大小 We will set the recovery model to simple. This mode reduces the entries in the log. However, you cannot restore the database in a specific time with this option.
我们将恢复模型设置为简单。 此模式将减少日志中的条目。 但是,您不能使用此选项在特定时间内还原数据库。
In the Database Properties, go to Options and in Recovery model, select simple:
在数据库属性中,转到选项,然后在恢复模型中选择简单:
Figure 2. Recovery model 图2.恢复模型 You can also change the recovery model using T-SQL:
您还可以使用T-SQL更改恢复模型:
USE [master] GO ALTER DATABASE [deleteDemo] SET RECOVERY SIMPLE WITH NO_WAIT GO USE [master] GO ALTER DATABASE [truncateDemo] SET RECOVERY SIMPLE WITH NO_WAIT GO
In order to test the truncate and delete statements, we will create a table named myprices in each database with a million rows:
为了测试截断和删除语句,我们将在每个数据库中创建一个名为myprices的表,该表具有一百万行:
--Table truncateDemo Use truncateDemo GO with listprices as( select 1 id, CAST(RAND(CHECKSUM(NEWID()))*1000000 as int) prices union all select id + 1, CAST(RAND(CHECKSUM(NEWID()))*1000000 as int) prices from listprices where id <= 1000000 ) select * into myprices from listprices OPTION(MAXRECURSION 0) --Table deleteDemo Use deleteDemo GO with listprices as( select 1 id, CAST(RAND(CHECKSUM(NEWID()))*1000000 as int) prices union all select id + 1, CAST(RAND(CHECKSUM(NEWID()))*1000000 as int) prices from listprices where id <= 1000000 ) select * into myprices from listprices OPTION(MAXRECURSION 0)
Note that the size of the database has increased from 16 MB to 144. This is because of the large number of rows inserted. The log file grows even with a Recovery Model set to simple:
请注意,数据库的大小已从16 MB增加到144。这是因为插入了很多行。 即使将恢复模型设置为简单,日志文件也会增长:
Figure 3. Database Size after inserting a million rows 图3.插入一百万行后的数据库大小 We will first test the delete statement:
我们将首先测试delete语句:
USE deleteDemo GO DELETE FROM [dbo].[myprices]
As you can see, it takes 23 seconds to delete a million rows:
如您所见,删除一百万行需要23秒:
Figure 4. Time required to delete a million rows 图4.删除一百万行所需的时间 If we check the database properties, we will notice that the database size increased to 528 MB!
如果我们检查数据库属性,我们将注意到数据库大小增加到528 MB!
Figure 5. Database growth after deleting a million rows 图5.删除一百万行后数据库的增长 Now, we will try the truncate table statement:
现在,我们将尝试截断表语句:
USE truncateDemo GO TRUNCATE TABLE [dbo].[myprices]
When you run truncate, the execution time is 0 seconds:
当您运行truncate时,执行时间为0秒:
Figure 6. Truncate execution time 图6.截断执行时间 If we check the log file, we notice that the database size did not change!
如果我们检查日志文件,则会发现数据库大小没有改变!
Figure 7. Database size after truncating 图7.截断后的数据库大小 As you can see, you save space and the execution time is better with truncate. If you need to delete all the rows, it is a must to use it.
如您所见,您可以节省空间,并且使用truncate可以缩短执行时间。 如果需要删除所有行,则必须使用它。
- Do not use cursors Create or drop multiple objects because they are very slow 不要使用游标创建或删除多个对象,因为它们非常慢
In the next example, we will show 2 ways to delete multiple SQL Server Objects. One using cursors and another using CTEs to generate statements. We will see which option is faster.
在下一个示例中,我们将展示两种删除多个SQL Server对象的方法。 一个使用游标,另一个使用CTE生成语句。 我们将看到哪个选项更快。
First, we will create 10000 tables using T-SQL:
首先,我们将使用T-SQL创建10000个表:
with ctesequence as( select 1 id union all select id + 1 from ctesequence where id < 10000 ) select 'create table table'+cast(id as varchar(8))+'(id int)' from ctesequence OPTION(MAXRECURSION 0)
This code will create the following T-SQL statements:
此代码将创建以下T-SQL语句:
Create table table1 Create table table2 Create table table3 …. Create table table10000
Copy and paste the 10,000 create table statementsstatements and execute them.
复制并粘贴10,000个create table语句并执行它们。
You will have 10,000 tables created.
您将创建10,000个表。
We will first create a cursor named delete_tables that will drop all the tables that start with the name table:
我们将首先创建一个名为delete_tables的游标,该游标将删除所有以名称表开头的表:
--create a cursor named delete_tables DECLARE delete_tables CURSOR FAST_FORWARD FOR --The query will show all the tables whose name starts with the word table SELECT [TABLE_NAME] FROM [INFORMATION_SCHEMA].[TABLES] WHERE [TABLE_NAME] like 'table%' OPEN delete_tables DECLARE @tablename varchar(30) FETCH NEXT FROM delete_tables into @tablename WHILE @@FETCH_STATUS <> -1 BEGIN --we are dropping all the tables execute ('drop table '+@tablename ) FETCH NEXT FROM delete_tables into @tablename END CLOSE delete_tables DEALLOCATE delete_tables GO
The cursor will delete the 10,000 rows in 1 minute, 30 seconds:
光标将在1分钟30秒内删除10,000行:
Figure 8. Execution time 图8.执行时间 We will try now another method to delete 1,000 tables. Create the 1,000 tables again and run these statements:
现在,我们将尝试另一种删除1,000个表的方法。 再次创建1,000个表并运行以下语句:
with ctesequence as( select 1 id union all select id + 1 from ctesequence where id < 100000 ) select 'drop table table'+cast(id as varchar(8)) from ctesequence OPTION(MAXRECURSION 0)
This sentence will generate 10,000 statements to drop 10,000 tables. Copy and paste them and finally execute them:
这句话将生成10,000条语句以删除10,000个表。 复制并粘贴它们,最后执行它们:
Figure 9. 10000 drop statements took 1 minute 15 seconds 图9. 10000个drop语句花费了1分15秒 In this example, we are running drop statements sequentially in both cases. Running without cursors is a little bit faster because of the locks and the where clause used in the cursors. However, it is not a big difference in this case. The advantage of using cursors is that you delete them at once and you do not need to copy and paste the drop statements.
在此示例中,两种情况下我们都按顺序运行drop语句。 由于游标中使用了锁和where子句,因此没有游标的运行要快一些。 但是,在这种情况下,差别不大。 使用游标的优点是您可以一次删除它们,而无需复制和粘贴drop语句。
- Do not use cursors to convert or process row values because they are very slow 不要使用游标转换或处理行值,因为它们很慢
In the last example, we will convert rows to a column separated by commas.
在最后一个示例中,我们将行转换为以逗号分隔的列。
For example, if we have a table like this:
例如,如果我们有一个这样的表:
ID 1 2 3
ID 1 2 3
We want to have the following result:
我们希望得到以下结果:
1,2,3
1,2,3
We will create a table with 1000 values named #mysequence:
我们将创建一个包含1000个值的表,名为#mysequence:
with ctesequence as( select 1 id union all select id + 1 from ctesequence where id < 1000 ) select * into #mysequence from ctesequence OPTION(MAXRECURSION 0)
We will first try with cursors:
我们将首先尝试使用游标:
--Create a cursor DECLARE concatenate_values CURSOR FOR SELECT cast(id as varchar(30)) FROM #mysequence OPEN concatenate_values DECLARE @value varchar(max)='' DECLARE @initialvalue varchar(8)='' FETCH NEXT FROM concatenate_values into @initialvalue WHILE @@FETCH_STATUS <> -1 BEGIN --Concatenate the values SELECT @value= @value+','+@initialvalue FETCH NEXT FROM concatenate_values into @initialvalue END CLOSE concatenate_values DEALLOCATE concatenate_values select @value
The cursor will display the values separated by commas in 3 seconds:
光标将在3秒内显示用逗号分隔的值:
Figure 10. Cursor execution time 图10.游标执行时间 We will now try to use something different from cursors. To verify the time we will set statistics and time to on in order to have the execution time in milliseconds (ms):
现在,我们将尝试使用与游标不同的东西。 为了验证时间,我们将统计信息和时间设置为开,以使执行时间以毫秒(ms)为单位:
SET STATISTICS io ON SET STATISTICS time ON GO
We will first concatenate the values using a variable named @concatenatevalues and concatenate the values separated by commas:
我们将首先使用名为@concatenatevalues的变量将这些值连接起来,并用逗号分隔这些值:
declare @concatenatedvalues varchar(max) SET @concatenatedvalues = '' select @concatenatedvalues = @concatenatedvalues + cast(id as varchar(30))+ ',' from #mysequence select SUBSTRING(@concatenatedvalues, 0, LEN(@concatenatedvalues)) as [values]
These queries will take less than 100 ms in average:
这些查询平均需要不到100毫秒的时间:
Figure 11. Execution time 图11.执行时间 Another option is to use the XML PATH, this option is shorter, easier to read, but it takes longer time:
另一个选项是使用XML PATH,该选项更短,更易于阅读,但需要更长的时间:
SELECT ',' + cast(id as varchar(30)) FROM #mysequence FOR XML PATH('')
Figure 12. XML PATH solution 图12. XML PATH解决方案
结论 (Conclusions)
In this article, we verified that truncate is more efficient that a delete statement. In our example, we verified that truncating 1 million rows does not fill the log file and it takes less than 1 second. By the other hand, we found that the delete statement increased the database size from 144 Mb to 528 Mb. In addition, the execution time to delete was 23 seconds.
在本文中,我们验证了截断比删除语句更有效。 在我们的示例中,我们验证了截断一百万行不会填充日志文件,并且花费了不到1秒的时间。 另一方面,我们发现delete语句将数据库大小从144 Mb增加到528 Mb。 另外,删除的执行时间为23秒。
第一次测试 (First test)
Statement | Time (seconds) | Growth Size in Mb |
Truncate | 0 | 0 |
Delete | 23 | 384 |
声明 | 时间(秒) | 增长量(兆) |
截短 | 0 | 0 |
删除 | 23 | 384 |
Conclusion: When you need to delete all the rows, you should use Truncate.
结论:当需要删除所有行时,应使用Truncate。
第二次测试 (Second test)
In the second test, we dropped 10000 tables using cursors and then using CTE to generate the statements.
在第二个测试中,我们使用游标删除了10000个表,然后使用CTE生成了语句。
Cursors took 1 minute 30 seconds and CTE took 1 minute with 15 seconds.
游标花了1分钟30秒,CTE花了1分钟15秒。
Method | Time (seconds) |
Cursor | 90 |
CTE | 75 |
方法 | 时间(秒) |
光标 | 90 |
CTE | 75 |
The advantage of Cursors is that you can execute and delete the tables at once. In the CTE method, you need to generate the statements and after that, copy and paste the statements. The CTE code is simpler.
游标的优点是您可以一次执行和删除表。 在CTE方法中,您需要生成语句,然后复制并粘贴语句。 CTE代码更简单。
Conclusion: If you need to delete, create 100+ objects, using cursors is not a bad choice. Do not use Cursors if you can work with SETs of data.
结论:如果需要删除,使用光标创建100个以上的对象不是一个坏选择。 如果可以使用SET数据集,请不要使用Cursors。
第三次测试 (Third test)
In our third test, we converted 1000 rows into a single variable of 1000 values separated by commas.
在我们的第三个测试中,我们将1000行转换为1000个值的单个变量,并用逗号分隔。
We used 3 methods:
我们使用了3种方法:
- Cursors 游标
- Simple T-SQL 简单的T-SQL
- T-SQL with XML PATH mode 具有XML PATH模式的T-SQL
Method | Time (seconds) |
Cursor | 3 |
Simple T-SQL | 0.06 |
T-SQL with XML PATH | 0.3 |
方法 | 时间(秒) |
光标 | 3 |
简单的T-SQL | 0.06 |
具有XML PATH的T-SQL | 0.3 |
Conclusion: When you need to update rows in a table, delete rows, insert or search values, using cursors isn’t a good idea. As you can see in this example, it is extremely slow.
结论:当您需要更新表中的行,删除行,插入或搜索值时,使用游标不是一个好主意。 如您在本例中所见,它非常慢。
XML PATH is commonly used in many scenarios to manipulate and modify row values to replace the cursors. However, there are sometimes faster solutions.
XML PATH通常在许多情况下用于操纵和修改行值以替换游标。 但是,有时会有更快的解决方案。
翻译自: https://www.sqlshack.com/sql-server-performance-myth-busters/
sql server 性能