索引监控数据分析

网友投稿 270 2022-09-04

索引监控数据分析

通过上一节收集的数据组合在一起,并经过分析阶段,制定出对索引的创建、删除、修改方案,然后在实施阶段进行部署

主要关注下面几个部分:

1.审查服务器状态

2.未使用索引

3.索引计划使用

一:审查服务器状态

1.性能计数器

2.等待信息

3.Buffer分配

Use IndexDemoWITH CounterSummaryAS ( SELECT create_date ,server_name ,MAX(CASE WHEN counter_name = 'Forwarded Records/sec' THEN Calculated_Counter_value END) ForwardedRecords ,MAX(CASE WHEN counter_name = 'Forwarded Records/sec' THEN Calculated_Counter_value END) / (NULLIF(MAX(CASE WHEN counter_name = 'Batch Requests/sec' THEN Calculated_Counter_value END),0) * 10) AS ForwardedRecordRatio FROM dbo.IndexingCounters WHERE counter_name IN ('Forwarded Records/sec','Batch Requests/sec') GROUP BY create_date ,server_name)SELECT server_name ,MIN(ForwardedRecords) AS MinForwardedRecords ,AVG(ForwardedRecords) AS AvgForwardedRecords ,MAX(ForwardedRecords) AS MaxForwardedRecords ,MIN(ForwardedRecordRatio) AS MinForwardedRecordRatio ,AVG(ForwardedRecordRatio) AS AvgForwardedRecordRatio ,MAX(ForwardedRecordRatio) AS MaxForwardedRecordRatio ,100.*SUM(CASE WHEN ForwardedRecordRatio > 1 THEN 1 END) /COUNT(*) AS PctViolationFROM CounterSummaryGROUP BY server_name

创建一个堆表,插入数据,并更新其中一些数据,引发堆分页,最后执行上面的查询检查情况

USE IndexDemoGOIF OBJECT_ID('HeapExample', 'U') IS NOT NULL DROP TABLE HeapExampleCREATE TABLE dbo.HeapExample ( ID INT IDENTITY , FillerData VARCHAR(2000) )INSERT INTO dbo.HeapExample ( FillerData ) SELECT REPLICATE('X', 100) FROM sys.all_objectsUPDATE dbo.HeapExampleSET FillerData = REPLICATE('X', 2000)WHERE ID % 5 = 1GOSELECT *FROM dbo.HeapExampleWHERE ID % 3 = 1

当Forwarded Records发生,再次执行代码

如果存在Forward Records问题,通常有3种解决方案

1.更改数据类型,把变长改成定长,如varchar-char,不够这种情况可能不是很通用,而且可能伴随数据截断等问题

2.改变表的存储结构,也就是创建一个聚集索引在上面,从而移除堆表的组织数据机制

3.就是重建堆表

ALTER TABLE dbo.HeapExample REBUILD

2.Access Methods\FreeSpace Scans/sec(关于堆表的另一个计数器)

当在堆表中插入数据时,他会标识发生了什么操作,在插入过程中,可能会引起GAM、SGAM和PFS页的改动。如果插入的频率足够高,可能会在这些页上产生争用

通过汇总最小值、平均值和最大值来进行分析

WITH CounterSummary AS ( SELECT create_date , server_name , MAX(CASE WHEN counter_name = 'FreeSpace Scans/sec' THEN Calculated_Counter_value END) FreeSpaceScans , MAX(CASE WHEN counter_name = 'FreeSpace Scans/sec' THEN Calculated_Counter_value END) / ( NULLIF(MAX(CASE WHEN counter_name = 'Batch Requests/sec' THEN Calculated_Counter_value END), 0) * 10 ) AS ForwardedRecordRatio FROM dbo.IndexingCounters WHERE counter_name IN ( 'FreeSpace Scans/sec', 'Batch Requests/sec' ) GROUP BY create_date , server_name ) SELECT server_name , MIN(FreeSpaceScans) AS MinFreeSpaceScans , AVG(FreeSpaceScans) AS AvgFreeSpaceScans , MAX(FreeSpaceScans) AS MaxFreeSpaceScans , MIN(ForwardedRecordRatio) AS MinForwardedRecordRatio , AVG(ForwardedRecordRatio) AS AvgForwardedRecordRatio , MAX(ForwardedRecordRatio) AS MaxForwardedRecordRatio , 100. * SUM(CASE WHEN ForwardedRecordRatio > 1 THEN 1 END) / COUNT(*) AS PctViolation FROM CounterSummary GROUP BY server_name

如果FreeSpace Scans/sec很高,应该集中分析哪个堆有最高的插入数率。可以使用:sys.dm_db_index_operational_stats来查找

SELECT QUOTENAME(DB_NAME(database_id)) AS database_name , QUOTENAME(OBJECT_SCHEMA_NAME(object_id, database_id)) + '.' + QUOTENAME(OBJECT_NAME(object_id, database_id)) AS ObjectName , SUM(leaf_insert_count) AS leaf_insert_count , SUM(leaf_allocation_count) AS leaf_allocation_countFROM dbo.index_operational_stats_historyWHERE index_id = 0 AND database_id > 4GROUP BY object_id , database_idORDER BY leaf_insert_count DESC

在查找到根源之后,最好的方法就是加上聚集索引,改变其数据组织机制

3.Access Methods\Full Scans/sec,通过这个计数器可查看Full Scans/sec的值,这个值包含聚集、非聚集索引及堆表。高值意味着查询存在性能问题,这种情况可能会引起Page Life Expectancy(用于衡量内存压力的一个主要计数器)的变动,这将加大数据在内存中的存储时间,并引起I/O问题

下面的脚本用于分析当前Full Scans/sec的值,同样也需要考虑Batch Requests/sec 计数器,如果这两个值的比例超过1000,就需要引起注意

WITH CounterSummary AS ( SELECT create_date , server_name , MAX(CASE WHEN counter_name = 'Full Scans/sec' THEN Calculated_Counter_value END) FullScans , MAX(CASE WHEN counter_name = 'Full Scans/sec' THEN Calculated_Counter_value END) / ( NULLIF(MAX(CASE WHEN counter_name = 'Batch Requests/sec' THEN Calculated_Counter_value END), 0) * 1000 ) AS FullRatio FROM dbo.IndexingCounters WHERE counter_name IN ( 'Full Scans/sec', 'Batch Requests/sec' ) GROUP BY create_date , server_name ) SELECT server_name , MIN(FullScans) AS MinFullScans , AVG(FullScans) AS AvgFullScans , MAX(FullScans) AS MaxFullScans , MIN(FullRatio) AS MinFullRatio , AVG(FullRatio) AS AvgFullRatio , MAX(FullRatio) AS MaxFullRatio , 100. * SUM(CASE WHEN FullRatio > 1 THEN 1 ELSE 0 END) / COUNT(*) AS PctViolation FROM CounterSummary GROUP BY server_name

4. Access Methods\Index Searches/sec,大部分情况下,索引查找会比索引扫描有效,这个计数器显示SQL Server 实例上发生索引查找的比率,这个值相对于Full Scans/sec来说越高越好。这个比率是衡量索引是否有效的标准之一,这两个值的比率一般是1000:1

WITH CounterSummary AS ( SELECT create_date , server_name , MAX(CASE WHEN counter_name = 'Index Searches/sec' THEN Calculated_Counter_value END) IndexSearches , MAX(CASE WHEN counter_name = 'Index Searches/sec' THEN Calculated_Counter_value END) / ( NULLIF(MAX(CASE WHEN counter_name = 'Full Scans/sec' THEN Calculated_Counter_value END), 0) * 1000 ) AS SearchToScanRatio FROM dbo.IndexingCounters WHERE counter_name IN ( 'Index Searches/sec', 'Full Scans/sec' ) GROUP BY create_date , server_name ) SELECT server_name , MIN(IndexSearches) AS MinIndexSearches , AVG(IndexSearches) AS AvgIndexSearches , MAX(IndexSearches) AS MaxIndexSearches , MIN(SearchToScanRatio) AS MinSearchToScanRatio , AVG(SearchToScanRatio) AS AvgSearchToScanRatio , MAX(SearchToScanRatio) AS MaxSearchToScanRatio , 100. * SUM(CASE WHEN SearchToScanRatio > 1 THEN 1 END) / COUNT(*) AS PctViolation FROM CounterSummary GROUP BY server_name

5.Access Methods\Page Splits/sec 对于堆表上的Forwarded Records,聚集索引上的就是Page Splits,没有聚集索引的表就是堆表,堆表上的非聚集索引还是使用的Forwarded Records。Page Splits是一个比较消耗资源的操作,而且在拆页的时候会对原来的页加上排他锁,并且会产生碎片,所以应尽可能少用。下面的脚本用于汇总这方面的数据,一般的建议是每20个Batch Requests/sec 不应该多于一个Page Split/sec 即Batch Requests:Page Split应该接近20:1

USE IndexDemoGOWITH CounterSummary AS ( SELECT create_date , server_name , MAX(CASE WHEN counter_name = 'Page Splits/sec' THEN Calculated_Counter_value END) PageSplits , MAX(CASE WHEN counter_name = 'Page Splits/sec' THEN Calculated_Counter_value END) / ( NULLIF(MAX(CASE WHEN counter_name = 'Batch Requests/sec' THEN Calculated_Counter_value END), 0) * 20 ) AS FullRatio FROM dbo.IndexingCounters WHERE counter_name IN ( 'Page Splits/sec', 'Batch Requests/sec' ) GROUP BY create_date , server_name ) SELECT server_name , MIN(PageSplits) AS MinPageSplits , AVG(PageSplits) AS AvgPageSplits , MAX(PageSplits) AS MaxPageSplits , MIN(FullRatio) AS MinFullRatio , AVG(FullRatio) AS AvgFullRatio , MAX(FullRatio) AS MaxFullRatio , 100. * SUM(CASE WHEN FullRatio > 1 THEN 1 ELSE 0 END) / COUNT(*) AS PctViolation FROM CounterSummary GROUP BY server_name

6.Buffer Manager\Page Lookups/sec用于衡量实例中的请求在buffer pool里面查询的单独页数量。这个数值很高时,可能意味着不高效的执行计划,通常需要研究该执行计划。一般数值很高是因为执行计划中产生了大量的Page Lookups和Row Lookups。通常情况下,每个Batch Request/sec不应该超过100次这类的lookups

以下脚本通过对比Page Lookups/sec 和Batch Request/sec来初始化数据,包括最小值、最大值和平均值。

USE IndexDemoGOWITH CounterSummary AS ( SELECT create_date , server_name , MAX(CASE WHEN counter_name = 'Page Lookups/sec' THEN Calculated_Counter_value END) PageLookups , MAX(CASE WHEN counter_name = 'Page Lookups/sec' THEN Calculated_Counter_value END) / ( NULLIF(MAX(CASE WHEN counter_name = 'Batch Requests/sec' THEN Calculated_Counter_value END), 0) * 100 ) AS PageLookupRatio FROM dbo.IndexingCounters WHERE counter_name IN ( 'Page Lookups/sec', 'Batch Requests/sec' ) GROUP BY create_date , server_name ) SELECT server_name , MIN(PageLookups) AS MinPageLookups , AVG(PageLookups) AS AvgPageLookups , MAX(PageLookups) AS MaxPageLookups , MIN(PageLookupRatio) AS MinPageLookupRatio , AVG(PageLookupRatio) AS AvgPageLookupRatio , MAX(PageLookupRatio) AS MaxPageLookupRatio , 100. * SUM(CASE WHEN PageLookupRatio > 1 THEN 1 ELSE 0 END) / COUNT(*) AS PctViolation FROM CounterSummary GROUP BY server_name

7.Locks(*)\Lock Wait Time(ms).这类计数器更偏重于检查索引的压力情况。可以衡量SQL Server花在索引、表、页上锁资源的时间。没有可参考的值,但是可以作为历史数据,然后用最近监控的数据和这个历史数据对比,比值应该越低越好。

用下面的脚本来监控该计数器的值,以便建立一个基准

USE IndexDemoGOWITH CounterSummary AS ( SELECT create_date , server_name , instance_name , MAX(CASE WHEN counter_name = 'Lock Wait Time (ms)' THEN Calculated_Counter_value END) / 1000 LockWaitTime FROM dbo.IndexingCounters WHERE counter_name = 'Lock Wait Time (ms)' GROUP BY create_date , server_name , instance_name ) SELECT CONVERT(VARCHAR(50), MAX(create_date), 101) AS CounterDate , server_name , instance_name , MIN(LockWaitTime) AS MinLockWaitTime , AVG(LockWaitTime) AS AvgLockWaitTime , MAX(LockWaitTime) AS MaxLockWaitTime , STDEV(LockWaitTime) AS StdDevLockWaitTime FROM CounterSummary GROUP BY server_name , instance_name UNION ALL SELECT 'Baseline: ' + CONVERT(VARCHAR(50), start_date, 101) + ' -->' + CONVERT(VARCHAR(50), end_date, 101) , server_name , instance_name , minimum_counter_value / 1000 , maximum_counter_value / 1000 , average_counter_value / 1000 , standard_deviation_counter_value / 1000 FROM dbo.IndexingCountersBaseline WHERE counter_name = 'Lock Wait Time (ms)' ORDER BY instance_name , CounterDate DESC

由于锁通常具有实时性、易丢失性,所以这部分的监控可能需要稍微加大频率,并且做好历史记录。

8。Locks(*) NUmber of Deadlocks/sec。极端的情况下,不良的索引设计和过渡锁阻塞会引起死锁(Deadlocks),这种情况是绝对不能容忍的,可以使用下面的脚本监控并研究起因、解决方案

USE IndexDemoGOWITH CounterSummary AS ( SELECT create_date , server_name , Calculated_Counter_value AS NumberDeadlocks FROM dbo.IndexingCounters WHERE counter_name = 'Number of Deadlocks/sec' ) SELECT server_name , MIN(NumberDeadlocks) AS MinNumberDeadlocks , AVG(NumberDeadlocks) AS AvgNumberDeadlocks , MAX(NumberDeadlocks) AS MaxNumberDeadlocks , STDEV(NumberDeadlocks) AS StdDevNumberDeadlocks FROM CounterSummary GROUP BY server_name

(2)

USE IndexDemoGOWITH WaitStats AS ( SELECT DENSE_RANK() OVER ( ORDER BY w.create_date ASC ) AS RankID , --相同的排序相同,下一个小的会紧挨着排序 create_date , wait_type , waiting_tasks_count , wait_time_ms , max_wait_time_ms , signal_wait_time_ms , MIN(create_date) OVER ( ) AS min_create_date , MAX(create_date) OVER ( ) AS max_create_date FROM dbo.wait_stats_history w WHERE wait_type NOT IN ( 'CLR_SEMAPHORE', 'LAZYWRITER_SLEEP', 'RESOURCE_QUEUE', 'SLEEP_TASK', 'SLEEP_SYSTEMTASK', 'SQLTRACE_BUFFER_FLUSH', 'WAITFOR', 'LOGMGR_QUEUE', 'CHECKPOINT_QUEUE', 'REQUEST_FOR_DEADLOCK_SEARCH', 'XE_TIMER_EVENT', 'BROKER_TO_FLUSH', 'BROKER_TASK_STOP', 'CLR_MANUAL_EVENT', 'CLR_AUTO_EVENT', 'DISPATCHER_QUEUE_SEMAPHORE', 'FT_IFTS_SCHEDULER_IDLE_WAIT', 'XE_DISPATCHER_WAIT', 'XE_DISPATCHER_JOIN', 'SQLTRACE_INCREMENTAL_FLUSH_SLEEP', 'ONDEMAND_TASK_QUEUE', 'BROKER_EVENTHANDLER', 'SLEEP_BPOOL_FLUSH', 'DIRTY_PAGE_POLL', 'HADR_FILESTREAM_IOMGR_IOCOMPLETION' ) ) SELECT wait_type , DATEDIFF(ms, min_create_date, max_create_date) AS total_time_ms , --返回两个日期的时间 SUM(waiting_tasks_count) AS waiting_tasks_count , SUM(wait_time_ms) AS wait_time_ms , CAST(1. * SUM(wait_time_ms) / SUM(waiting_tasks_count) AS DECIMAL(18, 3)) AS avg_wait_time_ms ,--将某种数据类型的表达式显式转换为另一种数据类型 CAST(100. * SUM(wait_time_ms) / DATEDIFF(ms, min_create_date, max_create_date) AS DECIMAL(18, 3)) AS pct_time_in_wait , SUM(signal_wait_time_ms) AS signal_wait_time_ms , CAST(100. * SUM(signal_wait_time_ms) / NULLIF(SUM(wait_time_ms), 0) AS DECIMAL(18, 3)) AS pct_time_runnable FROM WaitStats WHERE RankID <> 1 GROUP BY wait_type , min_create_date , max_create_date ORDER BY SUM(wait_time_ms) DESC

Buffer分配

查看数据库中使用了多少内存的Buffer,具体数量可能不重要,但是对比不同数据库中的比例情况就很重要

可以通过sys.dm_os_buffer_descriptors来检查,这个DMV会显示在内存中的每个数据页,以及其中的信息,并且通过计算显示出每个库所使用的Buffer情况

SELECT LEFT(CASE database_id WHEN 32767 THEN 'ResourceDb' ELSE DB_NAME(database_id) END,20) AS Database_Name, COUNT(*) AS Buffered_Page_Count, CAST(COUNT(*)*8/1024.0 AS NUMERIC(10,2)) AS Buffer_Pool_MB FROM sys.dm_os_buffer_descriptors GROUP BY DB_NAME(database_id),database_id order BY Buffered_Page_Count desc

查看什么库使用了最多的Buffer,还可以改进一下,用它来查看占用了内存最多的表或者索引

WITH BufferAllocation AS ( SELECT object_id , index_id , allocation_unit_id FROM sys.allocation_units AS au INNER JOIN sys.partitions AS p ON au.container_id = p.hobt_id AND ( au.type = 1 OR au.type = 3 ) UNION ALL SELECT object_id , index_id , allocation_unit_id FROM sys.allocation_units AS au INNER JOIN sys.partitions AS p ON au.container_id = p.hobt_id AND au.type = 2 ) SELECT t.name , i.name , i.type_desc , COUNT(*) AS Buffered_Page_Count , CAST(COUNT(*) * 8 / 1024.0 AS NUMERIC(10, 2)) AS Buffer_MB FROM sys.tables t INNER JOIN BufferAllocation ba ON t.object_id = ba.object_id LEFT JOIN sys.indexes i ON ba.object_id = i.object_id AND ba.index_id = i.index_id INNER JOIN sys.dm_os_buffer_descriptors bd ON ba.allocation_unit_id = bd.allocation_unit_id WHERE bd.database_id = DB_ID() GROUP BY t.name , i.index_id , i.name , i.type_desc ORDER BY Buffered_Page_Count DESC

2.无用索引

对于索引分析,其中一个重要的部分是移除无用索引,因为索引是有成本的,包括存储成本和修改成本,所以没有被用过的索引应该去除,一直都没有用户操作使用过的索引,由于某些索引是用于维护数据的一致性的,因此可能也会没有用户操作发生在上面。下面的脚本用于查找无用索引。

SELECT OBJECT_NAME(i.object_id) AS table_name , COALESCE(i.name, SPACE(0)) AS index_name , ps.partition_number , ps.row_count , CAST(( ps.reserved_page_count * 8 ) / 1024. AS DECIMAL(12, 2)) AS size_in_mb , COALESCE(ius.user_seeks, 0) AS user_seeks , COALESCE(ius.user_scans, 0) AS user_scans , COALESCE(ius.user_lookups, 0) AS user_lookups , i.type_descFROM sys.all_objects t INNER JOIN sys.indexes i ON t.object_id = i.object_id INNER JOIN sys.dm_db_partition_stats ps ON i.object_id = ps.object_id AND i.index_id = ps.index_id LEFT OUTER JOIN sys.dm_db_index_usage_stats ius ON ius.database_id = DB_ID() AND i.object_id = ius.object_id AND i.index_id = ius.index_idWHERE i.type_desc NOT IN ( 'HEAP', 'CLUSTERED' ) AND i.is_unique = 0 AND i.is_primary_key = 0 AND i.is_unique_constraint = 0 AND COALESCE(ius.user_seeks, 0) <= 0 AND COALESCE(ius.user_scans, 0) <= 0 AND COALESCE(ius.user_lookups, 0) <= 0ORDER BY OBJECT_NAME(i.object_id) , i.name

以上的脚本可能会把主键查出来,主键主要用于维护数据行的可表示性

SELECT OBJECT_NAME(i.object_id) AS table_name , COALESCE(i.name, SPACE(0)) AS index_name , ps.partition_number , ps.row_count , CAST(( ps.reserved_page_count * 8 ) / 1024. AS DECIMAL(12, 2)) AS size_in_mb , COALESCE(ius.user_seeks, 0) AS user_seeks , COALESCE(ius.user_scans, 0) AS user_scans , COALESCE(ius.user_lookups, 0) AS user_lookups , i.type_descFROM sys.all_objects t INNER JOIN sys.indexes i ON t.object_id = i.object_id INNER JOIN sys.dm_db_partition_stats ps ON i.object_id = ps.object_id AND i.index_id = ps.index_id LEFT OUTER JOIN sys.dm_db_index_usage_stats ius ON ius.database_id = DB_ID() AND i.object_id = ius.object_id AND i.index_id = ius.index_idWHERE i.type_desc NOT IN ( 'HEAP', 'CLUSTERED' ) AND i.is_unique = 0 AND i.is_primary_key = 0 AND i.is_unique_constraint = 0 AND COALESCE(ius.user_seeks, 0) <= 0 AND COALESCE(ius.user_scans, 0) <= 0 AND COALESCE(ius.user_lookups, 0) <= 0ORDER BY OBJECT_NAME(i.object_id) , i.name

3.索引计划使用:

用于查看多少个查询中使用了特定索引的所有执行计划,只需要替换脚本中的执行计划@IndexName。

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTEDGODECLARE @IndexName SYSNAME = 'PK_SalesOrderHeader_SalesOrderID';SET @IndexName = QUOTENAME(@IndexName, '[');WITH XMLNAMESPACES (DEFAULT 'Server/2004/07/showplan'),IndexSearchAS ( SELECT qp.query_plan ,cp.usecounts ,ix.query('.') AS StmtSimple FROM sys.dm_exec_cached_plans cp OUTER APPLY sys.dm_exec_query_plan(cp.plan_handle) qp CROSS APPLY qp.query_plan.nodes('//StmtSimple') AS p(ix) WHERE query_plan.exist('//Object[@Index = sql:variable("@IndexName")]') = 1)SELECT StmtSimple.value('StmtSimple[1]/@StatementText', 'VARCHAR(4000)') AS sql_text ,obj.value('@Database','sysname') AS database_name ,obj.value('@Schema','sysname') AS schema_name ,obj.value('@Table','sysname') AS table_name ,obj.value('@Index','sysname') AS index_name ,ixs.query_planFROM IndexSearch ixs CROSS APPLY StmtSimple.nodes('//Object') AS o(obj)WHERE obj.exist('//Object[@Index = sql:variable("@IndexName")]') = 1

通过增加物理操作符的名称来定位

DECLARE @IndexName SYSNAME = 'IX_SalesOrderHeader_SalesPersonID';DECLARE @op SYSNAME = 'Index Scan';;WITH XMLNAMESPACES(DEFAULT N'Server/2004/07/showplan')SELECT cp.plan_handle ,DB_NAME(dbid) + '.' + OBJECT_SCHEMA_NAME(objectid, dbid) + '.' + OBJECT_NAME(objectid, dbid) AS database_object ,qp.query_plan ,c1.value('@PhysicalOp','nvarchar(50)') ,c2.value('@Index','nvarchar(max)')FROM sys.dm_exec_cached_plans cp CROSS APPLY sys.dm_exec_query_plan(cp.plan_handle) qp CROSS APPLY query_plan.nodes('//RelOp') r(c1) OUTER APPLY c1.nodes('IndexScan/Object') AS o(c2)WHERE c2.value('@Index','nvarchar(max)') = QUOTENAME(@IndexName,'[')AND c1.exist('@PhysicalOp[. = sql:variable("@op")]') = 1

通过这些脚本定位可能有问题的执行计划,通过执行计划分析得出解决方案

找出使用频率最高的20%的查询

SELECT TOP 20 PERCENT cp.usecounts AS '使用次数' , cp.cacheobjtype AS '缓存类型' , cp.objtype AS [对象类型] , st.text AS 'TSQL' , --cp.plan_handle AS '执行计划', qp.query_plan AS '执行计划' , cp.size_in_bytes AS '执行计划占用空间(Byte)'FROM sys.dm_exec_cached_plans cp CROSS APPLY sys.dm_exec_sql_text(plan_handle) st CROSS APPLY sys.dm_exec_query_plan(plan_handle) qpORDER BY usecounts DESC

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:SQL Server等待
下一篇:这波营销太圈粉!雷军宣布给小米首批用户赠送1999红包!共3.7亿!
相关文章

 发表评论

暂时没有评论,来抢沙发吧~