TLDR:
Rất nhiều câu trả lời với tuyên bố về hiệu suất và thực hành xấu, vì vậy tôi làm rõ điều đó ở đây.
Tuyến ngoại lệ nhanh hơn cho số cột được trả về cao hơn, tuyến vòng lặp nhanh hơn cho số cột thấp hơn và điểm giao nhau là khoảng 11 cột. Cuộn xuống phía dưới để xem biểu đồ và mã kiểm tra.
Câu trả lời đầy đủ:
Mã cho một số câu trả lời hàng đầu hoạt động, nhưng có một cuộc tranh luận cơ bản ở đây cho câu trả lời "tốt hơn" dựa trên sự chấp nhận xử lý ngoại lệ trong logic và hiệu suất liên quan.
Để xóa điều đó, tôi không tin rằng có nhiều hướng dẫn liên quan đến các ngoại lệ CATCHING. Microsoft có một số hướng dẫn liên quan đến các trường hợp ngoại lệ. Ở đó họ làm nhà nước:
KHÔNG sử dụng ngoại lệ cho luồng điều khiển thông thường, nếu có thể.
Lưu ý đầu tiên là sự khoan hồng của "nếu có thể". Quan trọng hơn, mô tả cho bối cảnh này:
framework designers should design APIs so users can write code that does not throw exceptions
Điều đó có nghĩa là nếu bạn đang viết một API có thể bị người khác sử dụng, hãy cho họ khả năng điều hướng một ngoại lệ mà không cần thử / bắt. Ví dụ: cung cấp một TryPude với phương pháp Parse ném ngoại lệ của bạn. Không nơi nào nói điều này mặc dù bạn không nên bắt một ngoại lệ.
Hơn nữa, như một người dùng khác chỉ ra, các sản phẩm khai thác luôn cho phép lọc theo loại và gần đây cho phép lọc thêm thông qua mệnh đề khi . Điều này có vẻ như lãng phí các tính năng ngôn ngữ nếu chúng ta không nên sử dụng chúng.
Có thể nói rằng có MỘT SỐ chi phí cho một ngoại lệ bị ném và chi phí đó CÓ THỂ ảnh hưởng đến hiệu suất trong một vòng lặp nặng. Tuy nhiên, cũng có thể nói rằng chi phí của một ngoại lệ sẽ không đáng kể trong một "ứng dụng được kết nối". Chi phí thực tế đã được điều tra hơn một thập kỷ trước: https://stackoverflow.com/a/891230/852208
Nói cách khác, chi phí kết nối và truy vấn của cơ sở dữ liệu có thể thấp hơn ngoại lệ bị ném.
Ngoài ra, tôi muốn xác định phương pháp nào thực sự nhanh hơn. Như dự kiến không có câu trả lời cụ thể.
Bất kỳ mã nào lặp trên các cột sẽ trở nên chậm hơn khi số lượng cột tồn tại. Cũng có thể nói rằng bất kỳ mã nào dựa trên các ngoại lệ sẽ chậm tùy thuộc vào tốc độ không tìm thấy truy vấn.
Nhận câu trả lời của cả Chad Grant và Matt Hamilton, tôi đã chạy cả hai phương pháp với tối đa 20 cột và tỷ lệ lỗi lên tới 50% (OP cho biết anh ta đang sử dụng hai bài kiểm tra này giữa các procs khác nhau, vì vậy tôi giả sử chỉ có hai) .
Dưới đây là kết quả, được vẽ với LinqPad:
Các đường ngoằn ngoèo ở đây là tỷ lệ lỗi (không tìm thấy cột) trong mỗi số cột.
Trong các tập kết quả hẹp hơn, vòng lặp là một lựa chọn tốt. Tuy nhiên, phương thức GetOrdinal / Exception gần như không nhạy cảm với số lượng cột và bắt đầu vượt trội so với phương thức lặp ngay khoảng 11 cột.
Điều đó nói rằng tôi thực sự không có hiệu suất ưu tiên vì 11 cột nghe có vẻ hợp lý vì số cột trung bình được trả về trên toàn bộ ứng dụng. Trong cả hai trường hợp, chúng ta đang nói về phân số của một phần nghìn giây ở đây.
Tuy nhiên, từ khía cạnh đơn giản mã và hỗ trợ bí danh, có lẽ tôi sẽ đi theo tuyến đường GetOrdinal.
Đây là bài kiểm tra ở dạng linqpad. Vui lòng đăng lại bằng phương pháp của riêng bạn:
void Main()
{
var loopResults = new List<Results>();
var exceptionResults = new List<Results>();
var totalRuns = 10000;
for (var colCount = 1; colCount < 20; colCount++)
{
using (var conn = new SqlConnection(@"Data Source=(localdb)\MSSQLLocalDb;Initial Catalog=master;Integrated Security=True;"))
{
conn.Open();
//create a dummy table where we can control the total columns
var columns = String.Join(",",
(new int[colCount]).Select((item, i) => $"'{i}' as col{i}")
);
var sql = $"select {columns} into #dummyTable";
var cmd = new SqlCommand(sql,conn);
cmd.ExecuteNonQuery();
var cmd2 = new SqlCommand("select * from #dummyTable", conn);
var reader = cmd2.ExecuteReader();
reader.Read();
Func<Func<IDataRecord, String, Boolean>, List<Results>> test = funcToTest =>
{
var results = new List<Results>();
Random r = new Random();
for (var faultRate = 0.1; faultRate <= 0.5; faultRate += 0.1)
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
var faultCount=0;
for (var testRun = 0; testRun < totalRuns; testRun++)
{
if (r.NextDouble() <= faultRate)
{
faultCount++;
if(funcToTest(reader, "colDNE"))
throw new ApplicationException("Should have thrown false");
}
else
{
for (var col = 0; col < colCount; col++)
{
if(!funcToTest(reader, $"col{col}"))
throw new ApplicationException("Should have thrown true");
}
}
}
stopwatch.Stop();
results.Add(new UserQuery.Results{
ColumnCount = colCount,
TargetNotFoundRate = faultRate,
NotFoundRate = faultCount * 1.0f / totalRuns,
TotalTime=stopwatch.Elapsed
});
}
return results;
};
loopResults.AddRange(test(HasColumnLoop));
exceptionResults.AddRange(test(HasColumnException));
}
}
"Loop".Dump();
loopResults.Dump();
"Exception".Dump();
exceptionResults.Dump();
var combinedResults = loopResults.Join(exceptionResults,l => l.ResultKey, e=> e.ResultKey, (l, e) => new{ResultKey = l.ResultKey, LoopResult=l.TotalTime, ExceptionResult=e.TotalTime});
combinedResults.Dump();
combinedResults
.Chart(r => r.ResultKey, r => r.LoopResult.Milliseconds * 1.0 / totalRuns, LINQPad.Util.SeriesType.Line)
.AddYSeries(r => r.ExceptionResult.Milliseconds * 1.0 / totalRuns, LINQPad.Util.SeriesType.Line)
.Dump();
}
public static bool HasColumnLoop(IDataRecord dr, string columnName)
{
for (int i = 0; i < dr.FieldCount; i++)
{
if (dr.GetName(i).Equals(columnName, StringComparison.InvariantCultureIgnoreCase))
return true;
}
return false;
}
public static bool HasColumnException(IDataRecord r, string columnName)
{
try
{
return r.GetOrdinal(columnName) >= 0;
}
catch (IndexOutOfRangeException)
{
return false;
}
}
public class Results
{
public double NotFoundRate { get; set; }
public double TargetNotFoundRate { get; set; }
public int ColumnCount { get; set; }
public double ResultKey {get => ColumnCount + TargetNotFoundRate;}
public TimeSpan TotalTime { get; set; }
}