C#中SQL Server數(shù)據(jù)庫(kù)調(diào)優(yōu)的基礎(chǔ)方法
1. 什么是數(shù)據(jù)庫(kù)調(diào)優(yōu)?
想象一下,數(shù)據(jù)庫(kù)就像一個(gè)大圖書(shū)館,調(diào)優(yōu)就是讓圖書(shū)管理員(數(shù)據(jù)庫(kù))更快地找到你要的書(shū)(數(shù)據(jù))。
簡(jiǎn)單說(shuō):數(shù)據(jù)庫(kù)調(diào)優(yōu)就是讓數(shù)據(jù)庫(kù)運(yùn)行得更快、更穩(wěn)定!
2. 為什么要調(diào)優(yōu)?
先看一個(gè)反面例子(不好的代碼):
// ? 糟糕的寫(xiě)法 - 性能很差
public List<User> GetUserOrders(int userId)
{
var users = new List<User>();
using (var connection = new SqlConnection(connectionString))
{
connection.Open();
// 問(wèn)題1:在循環(huán)中執(zhí)行SQL查詢(xún)(N+1問(wèn)題)
var userSql = "SELECT * FROM Users WHERE IsActive = 1";
using (var userCommand = new SqlCommand(userSql, connection))
{
using (var reader = userCommand.ExecuteReader())
{
while (reader.Read())
{
var user = new User
{
Id = (int)reader["Id"],
Name = (string)reader["Name"]
};
// 問(wèn)題2:為每個(gè)用戶(hù)單獨(dú)查詢(xún)訂單
var orderSql = $"SELECT * FROM Orders WHERE UserId = {user.Id}";
using (var orderCommand = new SqlCommand(orderSql, connection))
{
using (var orderReader = orderCommand.ExecuteReader())
{
while (orderReader.Read())
{
user.Orders.Add(new Order
{
Id = (int)orderReader["Id"],
Amount = (decimal)orderReader["Amount"]
});
}
}
}
users.Add(user);
}
}
}
}
return users;
}
3. C# 代碼層面的調(diào)優(yōu)技巧
技巧1:使用參數(shù)化查詢(xún)(防止SQL注入+性能提升)
// ? 好的寫(xiě)法
public User GetUserById(int userId)
{
using (var connection = new SqlConnection(connectionString))
{
connection.Open();
// 使用參數(shù)化查詢(xún)
var sql = "SELECT Id, Name, Email FROM Users WHERE Id = @UserId AND IsActive = 1";
using (var command = new SqlCommand(sql, connection))
{
// 添加參數(shù)
command.Parameters.AddWithValue("@UserId", userId);
using (var reader = command.ExecuteReader())
{
if (reader.Read())
{
return new User
{
Id = (int)reader["Id"],
Name = (string)reader["Name"],
Email = (string)reader["Email"]
};
}
}
}
}
return null;
}
技巧2:一次性獲取數(shù)據(jù)(解決N+1問(wèn)題)
// ? 好的寫(xiě)法 - 使用 JOIN 一次性獲取數(shù)據(jù)
public List<User> GetUsersWithOrders()
{
var users = new List<User>();
using (var connection = new SqlConnection(connectionString))
{
connection.Open();
// 使用 JOIN 一次性獲取用戶(hù)和訂單數(shù)據(jù)
var sql = @"
SELECT u.Id, u.Name, o.Id as OrderId, o.Amount, o.OrderDate
FROM Users u
LEFT JOIN Orders o ON u.Id = o.UserId
WHERE u.IsActive = 1
ORDER BY u.Id, o.OrderDate DESC";
using (var command = new SqlCommand(sql, connection))
{
using (var reader = command.ExecuteReader())
{
User currentUser = null;
while (reader.Read())
{
int userId = (int)reader["Id"];
// 如果是新用戶(hù),創(chuàng)建用戶(hù)對(duì)象
if (currentUser == null || currentUser.Id != userId)
{
currentUser = new User
{
Id = userId,
Name = (string)reader["Name"],
Orders = new List<Order>()
};
users.Add(currentUser);
}
// 添加訂單(如果有)
if (!reader.IsDBNull(reader.GetOrdinal("OrderId")))
{
currentUser.Orders.Add(new Order
{
Id = (int)reader["OrderId"],
Amount = (decimal)reader["Amount"],
OrderDate = (DateTime)reader["OrderDate"]
});
}
}
}
}
}
return users;
}
技巧3:合理使用連接池
// ? 好的寫(xiě)法 - 連接字符串中啟用連接池
// 在配置文件中:
// "Server=.;Database=MyDB;Integrated Security=true;Max Pool Size=100;Min Pool Size=10;"
public class UserRepository
{
private readonly string _connectionString;
public UserRepository(string connectionString)
{
_connectionString = connectionString;
}
public async Task<User> GetUserAsync(int userId)
{
// .NET 會(huì)自動(dòng)管理連接池
using (var connection = new SqlConnection(_connectionString))
{
await connection.OpenAsync();
var sql = "SELECT * FROM Users WHERE Id = @UserId";
using (var command = new SqlCommand(sql, connection))
{
command.Parameters.AddWithValue("@UserId", userId);
using (var reader = await command.ExecuteReaderAsync())
{
if (await reader.ReadAsync())
{
return new User
{
Id = reader.GetInt32("Id"),
Name = reader.GetString("Name")
};
}
}
}
}
return null;
}
}
4. SQL Server 層面的調(diào)優(yōu)
技巧4:創(chuàng)建合適的索引
// 在C#中執(zhí)行創(chuàng)建索引的SQL(通常在數(shù)據(jù)庫(kù)遷移中執(zhí)行)
public async Task CreateIndexesAsync()
{
using (var connection = new SqlConnection(connectionString))
{
await connection.OpenAsync();
// 為經(jīng)常查詢(xún)的字段創(chuàng)建索引
var createIndexSql = @"
-- 為用戶(hù)表的常用查詢(xún)字段創(chuàng)建索引
CREATE INDEX IX_Users_Email ON Users(Email);
CREATE INDEX IX_Users_IsActive ON Users(IsActive);
CREATE INDEX IX_Orders_UserId_OrderDate ON Orders(UserId, OrderDate DESC);
-- 為訂單表的查詢(xún)字段創(chuàng)建索引
CREATE INDEX IX_Orders_OrderDate ON Orders(OrderDate);
CREATE INDEX IX_Orders_Status ON Orders(Status);";
using (var command = new SqlCommand(createIndexSql, connection))
{
await command.ExecuteNonQueryAsync();
}
}
}
技巧5:分頁(yè)查詢(xún)(避免一次性獲取大量數(shù)據(jù))
// ? 好的寫(xiě)法 - 分頁(yè)查詢(xún)
public async Task<List<User>> GetUsersPagedAsync(int pageNumber, int pageSize)
{
var users = new List<User>();
using (var connection = new SqlConnection(connectionString))
{
await connection.OpenAsync();
// 使用 OFFSET/FETCH 進(jìn)行分頁(yè)(SQL Server 2012+)
var sql = @"
SELECT Id, Name, Email, CreatedDate
FROM Users
WHERE IsActive = 1
ORDER BY CreatedDate DESC
OFFSET @Offset ROWS
FETCH NEXT @PageSize ROWS ONLY";
using (var command = new SqlCommand(sql, connection))
{
command.Parameters.AddWithValue("@Offset", (pageNumber - 1) * pageSize);
command.Parameters.AddWithValue("@PageSize", pageSize);
using (var reader = await command.ExecuteReaderAsync())
{
while (await reader.ReadAsync())
{
users.Add(new User
{
Id = reader.GetInt32("Id"),
Name = reader.GetString("Name"),
Email = reader.GetString("Email"),
CreatedDate = reader.GetDateTime("CreatedDate")
});
}
}
}
}
return users;
}
5. 使用 Entity Framework 的調(diào)優(yōu)技巧
技巧6:EF Core 性能優(yōu)化
// ? 好的寫(xiě)法 - 使用 EF Core 的優(yōu)化技巧
public class UserService
{
private readonly MyDbContext _context;
public UserService(MyDbContext context)
{
_context = context;
}
// 只查詢(xún)需要的字段(不要 SELECT *)
public async Task<List<UserDto>> GetActiveUsersAsync()
{
return await _context.Users
.Where(u => u.IsActive)
.Select(u => new UserDto // 使用DTO,不要返回整個(gè)實(shí)體
{
Id = u.Id,
Name = u.Name,
Email = u.Email
})
.AsNoTracking() // 只讀操作使用 AsNoTracking
.ToListAsync();
}
// 使用 Include 一次性加載關(guān)聯(lián)數(shù)據(jù)
public async Task<List<User>> GetUsersWithOrdersAsync()
{
return await _context.Users
.Where(u => u.IsActive)
.Include(u => u.Orders) // 一次性加載訂單
.ThenInclude(o => o.OrderDetails) // 加載訂單詳情
.AsNoTracking()
.ToListAsync();
}
// 分頁(yè)查詢(xún)
public async Task<PagedResult<UserDto>> GetUsersPagedAsync(int page, int pageSize)
{
var query = _context.Users
.Where(u => u.IsActive)
.OrderBy(u => u.Name);
var totalCount = await query.CountAsync();
var items = await query
.Skip((page - 1) * pageSize)
.Take(pageSize)
.Select(u => new UserDto
{
Id = u.Id,
Name = u.Name,
Email = u.Email
})
.AsNoTracking()
.ToListAsync();
return new PagedResult<UserDto>(items, totalCount, page, pageSize);
}
}
6. 監(jiān)控和診斷
技巧7:添加性能監(jiān)控
public class MonitoringUserRepository
{
private readonly ILogger<MonitoringUserRepository> _logger;
public MonitoringUserRepository(ILogger<MonitoringUserRepository> logger)
{
_logger = logger;
}
public async Task<User> GetUserWithMonitoringAsync(int userId)
{
var stopwatch = Stopwatch.StartNew();
try
{
using (var connection = new SqlConnection(connectionString))
{
await connection.OpenAsync();
var sql = "SELECT * FROM Users WHERE Id = @UserId";
using (var command = new SqlCommand(sql, connection))
{
command.Parameters.AddWithValue("@UserId", userId);
using (var reader = await command.ExecuteReaderAsync())
{
if (await reader.ReadAsync())
{
return new User
{
Id = reader.GetInt32("Id"),
Name = reader.GetString("Name")
};
}
}
}
}
return null;
}
finally
{
stopwatch.Stop();
_logger.LogInformation("數(shù)據(jù)庫(kù)查詢(xún)耗時(shí): {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
// 如果查詢(xún)時(shí)間超過(guò)閾值,記錄警告
if (stopwatch.ElapsedMilliseconds > 1000)
{
_logger.LogWarning("慢查詢(xún)警告: 獲取用戶(hù) {UserId} 耗時(shí) {ElapsedMilliseconds}ms",
userId, stopwatch.ElapsedMilliseconds);
}
}
}
}
7. 調(diào)優(yōu)檢查清單
給小白同學(xué)的簡(jiǎn)單檢查清單:
? C#代碼層面:
- 使用參數(shù)化查詢(xún)(不要拼接SQL字符串)
- 一次性獲取數(shù)據(jù)(避免N+1查詢(xún)問(wèn)題)
- 使用分頁(yè)(不要一次性獲取大量數(shù)據(jù))
- 及時(shí)關(guān)閉數(shù)據(jù)庫(kù)連接(使用using語(yǔ)句)
? SQL Server層面:
- 為經(jīng)常查詢(xún)的字段創(chuàng)建索引
- 避免 SELECT *,只查詢(xún)需要的字段
- 大數(shù)據(jù)表使用分頁(yè)
- 定期維護(hù)數(shù)據(jù)庫(kù)(更新統(tǒng)計(jì)信息等)
? 架構(gòu)層面:
- 使用緩存(Redis等)減少數(shù)據(jù)庫(kù)壓力
- 讀寫(xiě)分離(查詢(xún)用從庫(kù),寫(xiě)入用主庫(kù))
- 考慮使用NoSQL處理非關(guān)系型數(shù)據(jù)
總結(jié)
數(shù)據(jù)庫(kù)調(diào)優(yōu)就像開(kāi)車(chē)時(shí)保養(yǎng)車(chē)輛:
- 好的代碼 = 良好的駕駛習(xí)慣
- 索引 = 給車(chē)輛加好機(jī)油
- 連接池 = 合理的加油站選擇
- 監(jiān)控 = 車(chē)輛的儀表盤(pán)
記住:調(diào)優(yōu)是一個(gè)持續(xù)的過(guò)程,不是一次性的任務(wù)。先從最簡(jiǎn)單的優(yōu)化開(kāi)始,逐步深入!
到此這篇關(guān)于C#中SQL Server數(shù)據(jù)庫(kù)調(diào)優(yōu)的基礎(chǔ)方法的文章就介紹到這了,更多相關(guān)C# SQL Server調(diào)優(yōu)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#使用Lambda表達(dá)式簡(jiǎn)化代碼的示例詳解
Lambda,希臘字母λ,在C#編程語(yǔ)言中,被引入為L(zhǎng)ambda表達(dá)式,表示為匿名函數(shù)(匿名方法)。本文將利用Lambda表達(dá)式進(jìn)行代碼的簡(jiǎn)化,感興趣的可以了解一下2022-12-12
C#?Timer控件學(xué)習(xí)之使用Timer解決按鈕冪等性問(wèn)題
Timer控件又稱(chēng)定時(shí)器控件或計(jì)時(shí)器控件,該控件的主要作用是按一定的時(shí)間間隔周期性地觸發(fā)一個(gè)名為T(mén)ick的事件,因此在該事件的代碼中可以放置一些需要每隔一段時(shí)間重復(fù)執(zhí)行的程序段,這篇文章主要介紹了關(guān)于C#使用Timer解決按鈕冪等性問(wèn)題的相關(guān)資料,需要的朋友可以參考下2022-10-10
C#使用ODBC與OLEDB連接數(shù)據(jù)庫(kù)的方法示例
這篇文章主要介紹了C#使用ODBC與OLEDB連接數(shù)據(jù)庫(kù)的方法,結(jié)合實(shí)例形式分析了C#基于ODBC與OLEDB實(shí)現(xiàn)數(shù)據(jù)庫(kù)連接操作簡(jiǎn)單操作技巧,需要的朋友可以參考下2017-05-05
如何利用C#正則表達(dá)式判斷是否是有效的文件及文件夾路徑
項(xiàng)目中少不了讀取或設(shè)置文件路徑的功能,如何才能對(duì)輸入的路徑是否合法進(jìn)行判斷呢?下面這篇文章主要給大家介紹了關(guān)于C#利用正則表達(dá)式判斷是否是有效的文件及文件夾路徑的相關(guān)資料,需要的朋友可以參考下2022-04-04
C#函數(shù)式編程中的遞歸調(diào)用之尾遞歸詳解
這篇文章主要介紹了C#函數(shù)式編程中的遞歸調(diào)用詳解,本文講解了什么是尾遞歸、尾遞歸的多種方式、尾遞歸的代碼實(shí)例等內(nèi)容,需要的朋友可以參考下2015-01-01
C#通過(guò)KD樹(shù)進(jìn)行距離最近點(diǎn)的查找
這篇文章主要為大家詳細(xì)介紹了C#通過(guò)KD樹(shù)進(jìn)行距離最近點(diǎn)的查找,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-09-09

