如何判断是不是正在访问数据库表?想要类似“SELECT 触发器”的东西
Posted
技术标签:
【中文标题】如何判断是不是正在访问数据库表?想要类似“SELECT 触发器”的东西【英文标题】:How can I tell if a database table is being accessed anymore? Want something like a "SELECT trigger"如何判断是否正在访问数据库表?想要类似“SELECT 触发器”的东西 【发布时间】:2011-01-10 11:12:17 【问题描述】:我有一个非常大的数据库,其中包含数百个表,经过多次产品升级,我确信其中有一半不再使用。我如何判断一个表是否正在被主动选择?我不能只使用 Profiler - 我不仅要观察几天以上,而且还有数千个存储过程,并且 Profiler 不会将 SP 调用转换为表访问调用。
我唯一能想到的就是在感兴趣的表上创建一个聚集索引,然后监视sys.dm_db_index_usage_stats
以查看聚集索引上是否有任何搜索或扫描,这意味着表中的数据是加载。但是,在每个表上添加聚簇索引是一个坏主意(出于多种原因),因为它实际上并不可行。
我还有其他选择吗?我一直想要一个像“SELECT 触发器”这样的功能,但可能还有其他原因导致 SQL Server 也没有这个功能。
解决方案:
感谢 Remus,为我指明了正确的方向。使用这些列,我创建了以下 SELECT,这正是我想要的。
WITH LastActivity (ObjectID, LastAction) AS
(
SELECT object_id AS TableName,
last_user_seek as LastAction
FROM sys.dm_db_index_usage_stats u
WHERE database_id = db_id(db_name())
UNION
SELECT object_id AS TableName,
last_user_scan as LastAction
FROM sys.dm_db_index_usage_stats u
WHERE database_id = db_id(db_name())
UNION
SELECT object_id AS TableName,
last_user_lookup as LastAction
FROM sys.dm_db_index_usage_stats u
WHERE database_id = db_id(db_name())
)
SELECT OBJECT_NAME(so.object_id) AS TableName,
MAX(la.LastAction) as LastSelect
FROM sys.objects so
LEFT
JOIN LastActivity la
on so.object_id = la.ObjectID
WHERE so.type = 'U'
AND so.object_id > 100
GROUP BY OBJECT_NAME(so.object_id)
ORDER BY OBJECT_NAME(so.object_id)
【问题讨论】:
有没有办法在服务器关闭时将此数据捕获到表中?这将允许长期跟踪。 好问题。只是对于其他正在查看此内容的人,如果您使用的是 2008,则可以将 CTE 中的查询替换为以下内容;SELECT object_id AS TableName, (SELECT Max(v) FROM (VALUES (last_user_seek), (last_user_scan), (last_user_lookup)) AS value(v)) as [LastAction] FROM sys.dm_db_index_usage_stats u WHERE database_id = db_id(db_name())
来自here 的示例
【参考方案1】:
对于 SQL Server 2008,您应该查看 SQL Auditing。这使您可以审核许多事情,包括对表的选择以及对文件或事件日志的报告。
【讨论】:
【参考方案2】:Re:Profiler,如果您监视SP:StmtCompleted,它将捕获在存储过程中执行的所有语句,以便捕获存储过程中的表访问。如果不是所有内容都通过存储过程,您可能还需要SQL:StmtCompleted 事件。
将有大量事件,因此由于跟踪的大小,长时间跟踪可能仍然不切实际。但是,您可以应用过滤器 - 例如其中 TextData 包含您要检查的表的名称。您可以在任何时候提供一个表名列表以供过滤并逐步处理它们。因此,如果这些表都没有被访问过,您应该不会收到任何跟踪事件。
即使你觉得这对你来说不是一个合适/可行的方法,我认为它值得扩展。
另一种解决方案是对源代码进行全局搜索以查找对表的引用。您可以查询存储过程定义以检查给定表的匹配项,或者只生成一个完整的数据库脚本并对其进行查找以查找表名。
【讨论】:
+1 用于源代码搜索。一些应用程序具有非常不常用的功能,这些功能可能永远不会被分析/审计捕获。唯一确定的方法是实际检查使用数据库的程序! 是的。我会更信任源代码搜索(只要您检查所有源代码!),因为这不涉及监视表上可能很少见/需要很长时间才能标记的实际命中(如何你监控多久了?!)。 针对该数据库的应用程序执行了足够多的临时 SQL,以至于存储的 proc 代码搜索无法捕获所有内容 - proc 主要用于报告和计划的内容。不过我喜欢这次——我从来没有意识到“StmtCompleted”的 SP 和 SQL 版本之间的区别 @rwmnau:如果它使用大量的 ad-hoc SQL,那么在源代码中查找表引用应该更容易,不是吗? 是和否 - 我们有 SP 和报告的代码,但该应用程序是专有的,我无权访问源代码。我们已经有足够长的时间了,我们非常清楚它是如何工作的,但我无法检查代码来确定。我的观点是,我不能只对 SP 代码进行 syscmets 搜索来查找表名,因为该应用程序执行了大量不会出现在 SP 中的临时任务。【参考方案3】:我曾考虑使用不同表的用户权限,但后来我记得您可以使用 ON LOGON 触发器打开跟踪,您可能会从中受益:
CREATE OR REPLACE TRIGGER SYS.ON_LOGON_ALL
AFTER LOGON ON DATABASE
WHEN (
USER 'MAX'
)
BEGIN
EXECUTE IMMEDIATE 'ALTER SESSION SET SQL_TRACE TRUE';
--EXECUTE IMMEDIATE 'alter session set events ''10046 trace name context forever level 12''';
EXCEPTION
WHEN OTHERS THEN
NULL;
END;
/
然后您可以检查您的跟踪文件。
【讨论】:
【参考方案4】:查看sys.dm_db_index_usage_stats。 last_user_xxx 列将包含上次从用户请求访问该表的时间。此表在服务器重新启动后重置其跟踪,因此您必须让它运行一段时间才能依赖其数据。
【讨论】:
等等——这真的像我想象的那么棒吗?我会做一些测试,但似乎我可以监视表上的“HEAP”虚拟索引是否有任何搜索/扫描/查找,这准确地告诉了我我想知道的内容。如果是这样的话,先生,您就是我的英雄。 它会做你认为它会做的事情,甚至更多。您可以区分未使用的表(0 个堆或 1 个聚集索引)和从未使用过的非聚集索引,因此您可以丢弃不必要的非聚集索引。请记住,当服务器重新启动时它会重置(实际上,当数据库上线时,它会为该数据库重置,详细信息......) 优秀。我将使用我使用您的提示开发的魔术查询来更新问题,但这很壮观。 请记住,一段时间不使用可能并不意味着没有使用。年度报告每年只能运行一次。用于支持其中一些不常见任务的表格在其余时间可能看不到很多操作(或任何操作)。 好点。事实证明,数据库并不像我预期的那样“未启用”,而且由于我们的服务器自 10 月以来一直在运行,因此我们有一个非常可靠的活动快照,包括到年底。再次感谢大家的帮助!【参考方案5】:这个解决方案比上面的解决方案更适合我。但是,仍然受限于服务器也没有重新启动,但仍然可以让您很好地了解未使用的表。
SELECT [name]
,[object_id]
,[principal_id]
,[schema_id]
,[parent_object_id]
,[type]
,[type_desc]
,[create_date]
,[modify_date]
,[is_ms_shipped]
,[is_published]
,[is_schema_published]
FROM [COMTrans].[sys].[all_objects]
where object_id not in (
select object_id from sys.dm_db_index_usage_stats
)
and type='U'
order by name
【讨论】:
这不提供活动数据,仅提供包含 DDL 信息的表列表。【参考方案6】:以下查询使用查询计划缓存来查看缓存中的任何现有计划中是否存在对表的引用。这不能保证 100% 准确(因为如果存在内存限制,查询计划会被清除),但可以用来获得有关表使用的一些见解。
SELECT schema_name(schema_id) as schemaName, t.name as tableName,
databases.name,
dm_exec_sql_text.text AS TSQL_Text,
dm_exec_query_stats.creation_time,
dm_exec_query_stats.execution_count,
dm_exec_query_stats.total_worker_time AS total_cpu_time,
dm_exec_query_stats.total_elapsed_time,
dm_exec_query_stats.total_logical_reads,
dm_exec_query_stats.total_physical_reads,
dm_exec_query_plan.query_plan
FROM sys.dm_exec_query_stats
CROSS APPLY sys.dm_exec_sql_text(dm_exec_query_stats.plan_handle)
CROSS APPLY sys.dm_exec_query_plan(dm_exec_query_stats.plan_handle)
INNER JOIN sys.databases ON dm_exec_sql_text.dbid = databases.database_id
RIGHT JOIN sys.tables t (NOLOCK) ON cast(dm_exec_query_plan.query_plan as varchar(max)) like '%' + t.name + '%'
【讨论】:
以上是关于如何判断是不是正在访问数据库表?想要类似“SELECT 触发器”的东西的主要内容,如果未能解决你的问题,请参考以下文章