查看 LINQ 生成的 SQL 脚本

很多时候我们希望看到 LINQ 最终生成的 SQL 语句,拿到 SQL Server 中执行,用执行计划工具分析性能瓶颈。


方案一:VS 插件(Linq to SQL Debug Visualizer)

注意:这个插件是十年前写的,目前已无人维护,最新 VS(如 VS2015)未测试是否可用。

D:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\Packages\DebuggerCode language: CSS (css)

调试时 hover 在 IQueryable 变量上即可查看生成的 SQL。


方案二:db.Log 输出(适合控制台/窗体程序)

using (var db = new DataClasses1DataContext())
{
    db.Log = Console.Out; // 直接输出到控制台

    var q = from s in db.Student
            where s.age > 12
            select s;
    var l = q.ToList();
}Code language: JavaScript (javascript)

局限:Console.Out 只适合控制台和窗体程序,不适合 ASP.NET Web 程序。

改进:输出到文件

using (var db = new DataClasses1DataContext())
{
    StreamWriter sw = new StreamWriter(
        Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "log.txt"));
    db.Log = sw;

    var q = from s in db.Student
            where s.age > 12
            select s;
    var l = q.ToList();

    sw.Flush();
    sw.Close();
}Code language: JavaScript (javascript)

问题:db.Log 输出的 SQL 带参数占位符(@p0),不能直接在 SQL Server 中执行。


方案三:GetCommand 生成可执行的 SQL 文件(改进版)

将参数声明为 DECLARE 变量,拼成可直接执行的 .sql 文件:

StreamWriter sw = new StreamWriter(
    Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "log.sql"));

DbCommand com = _db.GetCommand(t); // t 是 IQueryable 对象

foreach (DbParameter item in com.Parameters)
{
    sw.WriteLine(string.Format(
        "declare {0}  {1} set {0} = {2}",
        item.ParameterName,
        item.DbType == DbType.Int32
            ? "int"
            : (new DbType[] { DbType.AnsiString, DbType.Guid }.Contains(item.DbType)
                ? "varchar(100)"
                : item.DbType.ToString()),
        new DbType[] { DbType.Int32, DbType.Decimal }.Contains(item.DbType)
            ? item.Value.ToString()
            : "'" + item.Value + "'"
    ));
}
sw.Write(com.CommandText);
sw.Flush();
sw.Close();Code language: PHP (php)

生成的文件内容类似:

declare @p0 int set @p0 = 12
SELECT [t0].[ID], [t0].[Name], [t0].[age]
FROM [Student] AS [t0]
WHERE [t0].[age] > @p0Code language: PHP (php)

可以直接在 SQL Server Management Studio 中执行。


方案四:直接替换参数

将参数占位符直接替换为实际值,生成纯 SQL,方便选取某一段单独执行:

StreamWriter sw = new StreamWriter(
    Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "log.sql"));

DbCommand com = _db.GetCommand(t); // t 是 IQueryable 对象
var sql = com.CommandText;
var noQuote = new DbType[] { DbType.Int32, DbType.Decimal }; // 不带单引号的类型

// 从后往前替换,避免 @p0 和 @p0_1 互相干扰
for (int i = com.Parameters.Count - 1; i >= 0; i--)
{
    var item = com.Parameters[i];
    sql = sql.Replace(
        item.ParameterName,
        noQuote.Contains(item.DbType)
            ? item.Value.ToString() + " "
            : "'" + item.Value + "'"
    );
}
sw.Write(sql);
sw.Flush();
sw.Close();Code language: JavaScript (javascript)

生成的 SQL:

SELECT [t0].[ID], [t0].[Name], [t0].[age]
FROM [Student] AS [t0]
WHERE [t0].[age] > 12Code language: CSS (css)

优点:​ 可以直接复制某一段 SQL 到 SQL Server 中执行,配合执行计划工具分析哪些地方耗时。


用 SQL Server 执行计划分析性能

拿到可执行的 SQL 后,在 SQL Server Management Studio 中:

  1. 点击 “包括实际的执行计划”(Ctrl + M)
  2. 执行 SQL
  3. 查看执行计划面板,找出:
    • 表扫描(Table Scan)​ → 缺少索引
    • 键查找(Key Lookup)​ → 需要覆盖索引
    • 高开销操作​ → 优化方向

给表连接字段增加索引

执行计划中发现表扫描时,给连接字段加索引:

-- 给外键字段加索引(最常见)
CREATE INDEX IX_Student_ClassID ON Student(ClassID);

-- 给经常作为条件的字段加索引
CREATE INDEX IX_Student_Name ON Student(Name);

-- 复合索引(多条件查询)
CREATE INDEX IX_Student_ClassID_Name ON Student(ClassID, Name);
场景建议
JOIN 字段必须加索引
WHERE 频繁过滤的字段加索引
ORDER BY 字段加索引可避免排序
低区分度字段(如 gender)不建议单独加索引

四种方案对比

方案适用场景能否直接执行维护状态
VS 插件 Debug Visualizer调试时快速查看需手动复制已停止维护
db.Log = Console.Out控制台/窗体带参数不能直接执行可用
db.Log 写文件所有程序带参数不能直接执行可用
GetCommand + 参数声明所有程序可直接执行可用
GetCommand + 直接替换值所有程序可直接执行,最方便推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注