SQL索引管理器 - 用於SQL Server和Azure上的索引維護的免費GUI工具

来源:https://www.cnblogs.com/langda/archive/2019/07/20/11219420.html
-Advertisement-
Play Games

我作為SQL Server DBA工作了8年多,管理和優化伺服器的性能。在我的空閑時間,我想為宇宙和我的同事做一些有用的事情。這就是我們最終為SQL Server和Azure 提供免費索引維護工具的方法。 理念 每隔一段時間,人們在處理他們的優先事項時,可能就像一個手指式電池 - 一個激勵充電只持續 ...


我作為SQL Server DBA工作了8年多,管理和優化伺服器的性能。在我的空閑時間,我想為宇宙和我的同事做一些有用的事情。這就是我們最終為SQL Server和Azure 提供免費索引維護工具的方法。

理念

每隔一段時間,人們在處理他們的優先事項時,可能就像一個手指式電池 - 一個激勵充電只持續一閃,然後一切都消失了。直到最近,我在這一生活觀察中也不例外。我經常被想法創造屬於我自己的想法所困擾,但優先順序從一個變為另一個並且沒有完成任何事情。

DevArt開發用於開發和管理SQL Server,MySQL和Oracle資料庫的軟體,對我的動機和專業成長產生了很大的影響。

在他們來之前,我對創建自己的產品的具體細節知之甚少,但在此過程中,我獲得了很多關於SQL Server內部結構的知識。一年多以來,我一直致力於優化產品線中的查詢,逐漸開始瞭解市場上哪些功能比另一種功能更受歡迎。

在某個階段,製作一個新的利基產品的想法出現在我面前,但由於某些情況,這個想法沒有成功。那時,基本上我沒有為公司內部的新項目找到足夠的資源而不影響核心業務。

在一個嶄新的地方工作,並試圖自己創建一個項目讓我不斷妥協。製造一個擁有所有花里胡哨的大產品的最初想法很快就會停止並逐漸轉變為一個不同的方向 - 將計劃的功能分解為單獨的迷你工具並相互獨立地實現它們。

因此,SQL Index Manager誕生了,它是SQL Server和Azure的免費索引維護工具。主要想法是將RedGate和Devart公司的商業替代品作為基礎,並嘗試在我自己的項目中改進其功能。

SQL索引管理器 - 用於SQL Server和Azure上的索引維護的免費GUI工具

SQL索引管理器 - 用於SQL Server和Azure上的索引維護的免費GUI工具

履行

口頭上說,一切聽起來都很簡單......只需觀看幾個激勵視頻,打開“Rocky Balboa”模式,開始製作一款很酷的產品。但讓我們面對音樂,一切都不那麼樂觀,因為在使用系統表函數時存在許多陷阱,sys.dm_db_index_physical_stats同時,它是唯一可以從中獲取有關索引碎片的最新信息的地方。

從開發的最初幾天起,就有很好的機會在標準方案中製造沉悶的方式,並複製已經調試過的競爭應用程式的邏輯,同時添加一些自組織。但在分析了元數據的查詢後,我想做一些更優化的事情,由於大公司的官僚主義,它們永遠不會出現在他們的產品中。

在分析RedGate SQL索引管理器(v1.1.9.1378 - 每個用戶155美元)時,您可以看到應用程式使用一種非常簡單的方法:使用第一個查詢,我們獲得用戶表和視圖的列表,然後第二個,我們返回所選資料庫中所有索引的列表。

SELECT objects.name AS tableOrViewName
     , objects.object_id AS tableOrViewId
     , schemas.name AS schemaName
     , CAST(ISNULL(lobs.NumLobs, 0) AS BIT) AS ContainsLobs
     , o.is_memory_optimized
FROM sys.objects AS objects
JOIN sys.schemas AS schemas ON schemas.schema_id = objects.schema_id
LEFT JOIN (
    SELECT object_id
         , COUNT(*) AS NumLobs
    FROM sys.columns WITH (NOLOCK)
    WHERE system_type_id IN (34, 35, 99)
        OR max_length = -1
    GROUP BY object_id
) AS lobs ON objects.object_id = lobs.object_id
LEFT JOIN sys.tables AS o ON o.object_id = objects.object_id
WHERE objects.type = 'U'
    OR objects.type = 'V'

SELECT i.object_id AS tableOrViewId
     , i.name AS indexName
     , i.index_id AS indexId
     , i.allow_page_locks AS allowPageLocks
     , p.partition_number AS partitionNumber
     , CAST((c.numPartitions - 1) AS BIT) AS belongsToPartitionedIndex
FROM sys.indexes AS i
JOIN sys.partitions AS p ON p.index_id = i.index_id
                        AND p.object_id = i.object_id
JOIN (
    SELECT COUNT(*) AS numPartitions
         , object_id
         , index_id
    FROM sys.partitions
    GROUP BY object_id
           , index_id
) AS c ON c.index_id = i.index_id
      AND c.object_id = i.object_id
WHERE i.index_id > 0 -- ignore heaps
    AND i.is_disabled = 0
    AND i.is_hypothetical = 0

接下來,在while每個索引分區迴圈中,發送請求以確定其大小和碎片級別。在掃描結束時,客戶端上會顯示重量小於進入閾值的索引。

EXEC sp_executesql N'
SELECT index_id, avg_fragmentation_in_percent, page_count
FROM sys.dm_db_index_physical_stats(@databaseId, @objectId, @indexId, @partitionNr, NULL)'
    , N'@databaseId int,@objectId int,@indexId int,@partitionNr int'
    , @databaseId = 7, @objectId = 2133582639, @indexId = 1, @partitionNr = 1

EXEC sp_executesql N'
SELECT index_id, avg_fragmentation_in_percent, page_count
FROM sys.dm_db_index_physical_stats(@databaseId, @objectId, @indexId, @partitionNr, NULL)'
    , N'@databaseId int,@objectId int,@indexId int,@partitionNr int'
    , @databaseId = 7, @objectId = 2133582639, @indexId = 2, @partitionNr = 1

EXEC sp_executesql N'
SELECT index_id, avg_fragmentation_in_percent, page_count
FROM sys.dm_db_index_physical_stats(@databaseId, @objectId, @indexId, @partitionNr, NULL)'
    , N'@databaseId int,@objectId int,@indexId int,@partitionNr int'
    , @databaseId = 7, @objectId = 2133582639, @indexId = 3, @partitionNr = 1

在分析此應用程式的邏輯時,您可能會發現各種缺點。例如,在發送請求之前,不會檢查當前分區是否包含任何行以從掃描中排除空分區。

但是問題在另一個方面表現得更加尖銳 - 對伺服器的請求數量大約等於來自的總行數sys.partitions鑒於真實資料庫可以包含數萬個分區,這種細微差別可能導致對伺服器的大量類似請求。在資料庫位於遠程伺服器上的情況下,由於每個請求的執行中的網路延遲增加,掃描時間將更長,即使是最簡單的一個。

與RedGate不同,由DevArt開發的類似產品 - 用於SQL Server的dbForge索引管理器(v1.10.38 - 每用戶99美元)在一個大型查詢中接收信息,然後在客戶端上顯示所有內容:

SELECT SCHEMA_NAME(o.[schema_id]) AS [schema_name]
     , o.name AS parent_name
     , o.[type] AS parent_type
     , i.name
     , i.type_desc
     , s.avg_fragmentation_in_percent
     , s.page_count
     , p.partition_number
     , p.[rows]
     , ISNULL(lob.is_lob_legacy, 0) AS is_lob_legacy
     , ISNULL(lob.is_lob, 0) AS is_lob
     , CASE WHEN ds.[type] = 'PS' THEN 1 ELSE 0 END AS is_partitioned
FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, NULL) s
JOIN sys.partitions p ON s.[object_id] = p.[object_id]
                     AND s.index_id = p.index_id
                     AND s.partition_number = p.partition_number
JOIN sys.indexes i ON i.[object_id] = s.[object_id]
                  AND i.index_id = s.index_id
LEFT JOIN (
    SELECT c.[object_id]
         , index_id = ISNULL(i.index_id, 1)
         , is_lob_legacy = MAX(CASE WHEN c.system_type_id IN (34, 35, 99) THEN 1 END)
         , is_lob = MAX(CASE WHEN c.max_length = -1 THEN 1 END)
    FROM sys.columns c
    LEFT JOIN sys.index_columns i ON c.[object_id] = i.[object_id]
                                 AND c.column_id = i.column_id
                                 AND i.index_id > 0
    WHERE c.system_type_id IN (34, 35, 99)
        OR c.max_length = -1
    GROUP BY c.[object_id], i.index_id
) lob ON lob.[object_id] = i.[object_id]
     AND lob.index_id = i.index_id
JOIN sys.objects o ON o.[object_id] = i.[object_id]
JOIN sys.data_spaces ds ON i.data_space_id = ds.data_space_id
WHERE i.[type] IN (1, 2)
    AND i.is_disabled = 0
    AND i.is_hypothetical = 0
    AND s.index_level = 0
    AND s.alloc_unit_type_desc = 'IN_ROW_DATA'
    AND o.[type] IN ('U', 'V')

消除了競爭產品中類似請求的面紗的主要問題,但是這種實現的缺點是沒有額外的參數傳遞給sys.dm_db_index_physical_stats可以限制對明顯不必要的索引的掃描函數。實際上,這會導致獲取系統中所有索引的信息以及掃描階段不必要的磁碟負載。

值得一提的是,從中獲取的源碼數據sys.dm_db_index_physical_stats並未永久緩存在緩衝池中,因此在獲取有關索引碎片的信息時最小化物理讀取是我的應用程式開發過程中的優先任務之一。

經過多次實驗,我設法將掃描分為兩部分,將兩種方法結合起來。最初,一個大型請求通過過濾那些未包含在過濾範圍中的分區來預先確定分區的大小:

INSERT INTO #AllocationUnits (ContainerID, ReservedPages, UsedPages)
SELECT [container_id]
     , SUM([total_pages])
     , SUM([used_pages])
FROM sys.allocation_units WITH(NOLOCK)
GROUP BY [container_id]
HAVING SUM([total_pages]) BETWEEN @MinIndexSize AND @MaxIndexSize

接下來,我們只獲取包含數據的分區,以避免從空索引中進行不必要的讀取。

SELECT [object_id]
     , [index_id]
     , [partition_id]
     , [partition_number]
     , [rows]
     , [data_compression]
INTO #Partitions
FROM sys.partitions WITH(NOLOCK)
WHERE [object_id] > 255
    AND [rows] > 0
    AND [object_id] NOT IN (SELECT * FROM #ExcludeList)

根據設置,僅獲取用戶想要分析的索引類型(支持堆,群集/非群集索引和列存儲)。

INSERT INTO #Indexes
SELECT ObjectID         = i.[object_id]
     , IndexID          = i.index_id
     , IndexName        = i.[name]
     , PagesCount       = a.ReservedPages
     , UnusedPagesCount = a.ReservedPages - a.UsedPages
     , PartitionNumber  = p.[partition_number]
     , RowsCount        = ISNULL(p.[rows], 0)
     , IndexType        = i.[type]
     , IsAllowPageLocks = i.[allow_page_locks]
     , DataSpaceID      = i.[data_space_id]
     , DataCompression  = p.[data_compression]
     , IsUnique         = i.[is_unique]
     , IsPK             = i.[is_primary_key]
     , FillFactorValue  = i.[fill_factor]
     , IsFiltered       = i.[has_filter]
FROM #AllocationUnits a
JOIN #Partitions p ON a.ContainerID = p.[partition_id]
JOIN sys.indexes i WITH(NOLOCK) ON i.[object_id] = p.[object_id]

                               AND p.[index_id] = i.[index_id]
WHERE i.[type] IN (0, 1, 2, 5, 6)
    AND i.[object_id] > 255

之後,我們添加了一些魔法,並且......對於所有小的索引,我們通過重覆調用sys.dm_db_index_physical_stats具有所有參數的完整指示的函數來確定碎片的級別

INSERT INTO #Fragmentation (ObjectID, IndexID, PartitionNumber, Fragmentation)
SELECT i.ObjectID
     , i.IndexID
     , i.PartitionNumber
     , r.[avg_fragmentation_in_percent]
FROM #Indexes i
CROSS APPLY sys.dm_db_index_physical_stats_
    (@DBID, i.ObjectID, i.IndexID, i.PartitionNumber, 'LIMITED') r
WHERE i.PagesCount <= @PreDescribeSize
    AND r.[index_level] = 0
    AND r.[alloc_unit_type_desc] = 'IN_ROW_DATA'
    AND i.IndexType IN (0, 1, 2)

接下來,我們通過過濾掉額外的數據將所有可能的信息返回給客戶端:

SELECT i.ObjectID
     , i.IndexID
     , i.IndexName
     , ObjectName       = o.[name]
     , SchemaName       = s.[name]
     , i.PagesCount
     , i.UnusedPagesCount
     , i.PartitionNumber
     , i.RowsCount
     , i.IndexType
     , i.IsAllowPageLocks
     , u.TotalWrites
     , u.TotalReads
     , u.TotalSeeks
     , u.TotalScans
     , u.TotalLookups
     , u.LastUsage
     , i.DataCompression
     , f.Fragmentation
     , IndexStats       = STATS_DATE(i.ObjectID, i.IndexID)
     , IsLobLegacy      = ISNULL(lob.IsLobLegacy, 0)
     , IsLob            = ISNULL(lob.IsLob, 0)
     , IsSparse         = CAST(CASE WHEN p.ObjectID IS NULL THEN 0 ELSE 1 END AS BIT)
     , IsPartitioned    = CAST(CASE WHEN dds.[data_space_id] _
                          IS NOT NULL THEN 1 ELSE 0 END AS BIT)
     , FileGroupName    = fg.[name]
     , i.IsUnique
     , i.IsPK
     , i.FillFactorValue
     , i.IsFiltered
     , a.IndexColumns
     , a.IncludedColumns
FROM #Indexes i
JOIN sys.objects o WITH(NOLOCK) ON o.[object_id] = i.ObjectID
JOIN sys.schemas s WITH(NOLOCK) ON s.[schema_id] = o.[schema_id]
LEFT JOIN #AggColumns a ON a.ObjectID = i.ObjectID
                       AND a.IndexID = i.IndexID
LEFT JOIN #Sparse p ON p.ObjectID = i.ObjectID
LEFT JOIN #Fragmentation f ON f.ObjectID = i.ObjectID
                          AND f.IndexID = i.IndexID
                          AND f.PartitionNumber = i.PartitionNumber
LEFT JOIN (
    SELECT ObjectID      = [object_id]
         , IndexID       = [index_id]
         , TotalWrites   = NULLIF([user_updates], 0)
         , TotalReads    = NULLIF([user_seeks] + [user_scans] + [user_lookups], 0)
         , TotalSeeks    = NULLIF([user_seeks], 0)
         , TotalScans    = NULLIF([user_scans], 0)
         , TotalLookups  = NULLIF([user_lookups], 0)
         , LastUsage     = (
                                SELECT MAX(dt)
                                FROM (
                                    VALUES ([last_user_seek])
                                         , ([last_user_scan])
                                         , ([last_user_lookup])
                                         , ([last_user_update])
                                ) t(dt)
                           )
    FROM sys.dm_db_index_usage_stats WITH(NOLOCK)
    WHERE [database_id] = @DBID
) u ON i.ObjectID = u.ObjectID
   AND i.IndexID = u.IndexID
LEFT JOIN #Lob lob ON lob.ObjectID = i.ObjectID
                  AND lob.IndexID = i.IndexID
LEFT JOIN sys.destination_data_spaces dds WITH(NOLOCK) _
            ON i.DataSpaceID = dds.[partition_scheme_id]
            AND i.PartitionNumber = dds.[destination_id]
JOIN sys.filegroups fg WITH(NOLOCK) _
            ON ISNULL(dds.[data_space_id], i.DataSpaceID) = fg.[data_space_id]
WHERE o.[type] IN ('V', 'U')
    AND (
            f.Fragmentation >= @Fragmentation
        OR
            i.PagesCount > @PreDescribeSize
        OR
            i.IndexType IN (5, 6)
    )

之後,點請求確定大型索引的碎片級別。

EXEC sp_executesql N'
DECLARE @DBID INT = DB_ID()
SELECT [avg_fragmentation_in_percent]
FROM sys.dm_db_index_physical_stats(@DBID, @ObjectID, @IndexID, @PartitionNumber, ''LIMITED'')
WHERE [index_level] = 0
    AND [alloc_unit_type_desc] = ''IN_ROW_DATA'''
    , N'@ObjectID int,@IndexID int,@PartitionNumber int'
    , @ObjectId = 1044198770, @IndexId = 1, @PartitionNumber = 1

EXEC sp_executesql N'
DECLARE @DBID INT = DB_ID()
SELECT [avg_fragmentation_in_percent]
FROM sys.dm_db_index_physical_stats(@DBID, @ObjectID, @IndexID, @PartitionNumber, ''LIMITED'')
WHERE [index_level] = 0
    AND [alloc_unit_type_desc] = ''IN_ROW_DATA'''
    , N'@ObjectID int,@IndexID int,@PartitionNumber int'
    , @ObjectId = 1552724584, @IndexId = 0, @PartitionNumber = 1

由於這種方法,在生成請求時,我設法解決了競爭對手應用程式中遇到的掃描性能問題。這可能是它的終結,但在開發過程中,逐漸出現了各種新的想法,這使得擴大我的產品的應用範圍成為可能。

最初,實現了對使用的支持WAIT_AT_LOW_PRIORITY,然後可以使用DATA_COMPRESSIONFILL_FACTOR重建索引。

SQL索引管理器 - 用於SQL Server和Azure上的索引維護的免費GUI工具

該應用程式已被“撒上”以前未計劃的功能,如維護列存儲:

SELECT *
FROM (
    SELECT IndexID          = [index_id]
         , PartitionNumber  = [partition_number]
         , PagesCount       = SUM([size_in_bytes]) / 8192
         , UnusedPagesCount = ISNULL(SUM(CASE WHEN [state] = 1 _
                              THEN [size_in_bytes] END), 0) / 8192
         , Fragmentation    = CAST(ISNULL(SUM(CASE WHEN [state] = 1 _
                              THEN [size_in_bytes] END), 0)
                            * 100. / SUM([size_in_bytes]) AS FLOAT)
    FROM sys.fn_column_store_row_groups(@ObjectID)
    GROUP BY [index_id]
           , [partition_number]
) t
WHERE Fragmentation >= @Fragmentation
    AND PagesCount BETWEEN @MinIndexSize AND @MaxIndexSize

或者根據以下信息創建非聚簇索引的能力dm_db_missing_index

SELECT ObjectID     = d.[object_id]
     , UserImpact   = gs.[avg_user_impact]
     , TotalReads   = gs.[user_seeks] + gs.[user_scans]
     , TotalSeeks   = gs.[user_seeks]
     , TotalScans   = gs.[user_scans]
     , LastUsage    = ISNULL(gs.[last_user_scan], gs.[last_user_seek])
     , IndexColumns =
                CASE
                    WHEN d.[equality_columns] IS NOT NULL 
                                _AND d.[inequality_columns] IS NOT NULL
                        THEN d.[equality_columns] + ', ' + d.[inequality_columns]
                    WHEN d.[equality_columns] IS NOT NULL AND d.[inequality_columns] IS NULL
                        THEN d.[equality_columns]
                    ELSE d.[inequality_columns]
                END
     , IncludedColumns = d.[included_columns]
FROM sys.dm_db_missing_index_groups g WITH(NOLOCK)
JOIN sys.dm_db_missing_index_group_stats gs WITH(NOLOCK) _
                       ON gs.[group_handle] = g.[index_group_handle]
JOIN sys.dm_db_missing_index_details d WITH(NOLOCK) _
                       ON g.[index_handle] = d.[index_handle]
WHERE d.[database_id] = DB_ID()

結果和計劃

關鍵的是,開發計劃並沒有就此結束,因為我渴望進一步開發這個應用程式網站源碼。下一步是添加查找重覆(已完成)或未使用索引的功能,以及實現對在SQL Server中維護統計信息的完全支持。

現在市場上有很多付費解決方案。我想相信,由於自由定位,更優化的查詢以及各種有用的gismos的可用性,這個產品肯定會在日常任務中變得有用。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 分享基於EF6、Unitwork、Autofac的Repository模式設計 [TOC] 一、實現的思路和結構圖 Repository的共同性 有一些公共的方法(增刪改查), 這些方法無關於Repository操作的是哪個實體類,可以把這些方法定義成介面IRepository,然後有個基類Base ...
  • 鏈接:https://pan.baidu.com/s/1lbTL8UNQr4o45Z30J_YGLA提取碼:xr3z 複製這段內容後打開百度網盤手機App,操作更方便哦 ...
  • 先看一下系統自帶的線型文件acadiso.lin: 因為STANDARD是每個CAD文檔必須要有的文字樣式,同樣的,如果想更改系統自定義的帶文字的線型樣式,需要更改STANDARD 需要註意的是,文字(管線)兩側的空白並不對稱,這是因為文字(管線)或圖形的長度實際是占用了下一個段落的長度, 這裡就是 ...
  • 提起.Net中的 async/await,相信很多.neter 第一反應都會是非同步編程,其本質是語法糖,但繼續追查下去,既然是語法糖,那麼經過編譯之後,真正的代碼是什麼樣的,如何執行的?帶著這些疑問,通過網上資料的查詢,可以瞭解到編譯之後,是通過實現 IAsyncStateMachine 的一個狀態 ...
  • 1.C#創建Windows應用程式,Web服務,移動應用程式,客戶端 - 伺服器應用程式,資料庫應用程式等等。 2.NET Framework由公共語言運行時(CLR)和.NET Framework類庫組成。 CLR是.NET Framework的基礎。 它在執行時管理代碼,提供核心服務,如記憶體管理 ...
  • 一、簡介 Ingo Molnar 的實時補丁是完全開源的,它採用的實時實現技術完全類似於Timesys Linux,而且中斷線程化的代碼是基於TimeSys Linux的中斷線程化代碼的。這些實時實現技術包括:中斷線程化(包括IRQ和softirq)、用Mutex取代spinlock、優先順序繼承和死 ...
  • 一、LINUX文件類型 -:普通文件: 純文本文件(ascll) 配置文件 二進位文件(binary file):命令 數據格式文件(date):/var/log/wtmp d:目錄文件 l:鏈接文件 設備文件(/dev): b:block塊設備 c:character 字元集設備:一次性讀取,按順 ...
  • 重啟網路 /etc/init.d/networking restart 語言設置文件 /etc/default/locale apt 安裝deb保存目錄 /var/cache/apt/archives 字體修改目錄 /etc/fonts/conf.d 配置更新源文件 /etc/apt/sources ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...