前言
在前篇文章《SQL Server 創建賬號密碼復雜度問題》中,我們創建了一個具有增刪改查權限的數據庫賬號。然而,用戶在使用 DBeaver 連接該賬號導入 CSV 文件時遇到了報錯:

按理說,既然賬號具有 insert 權限,DBeaver 導入 CSV 應該能夠正常工作啊!為了驗證權限是否正確,我試了一下執行 INSERT 語句,數據可以插入成功。
因為 CSV 數據不多,我就先手動把幾十條數據先手動 insert 進去了,臨時解決了問題,但是后面要是有幾千條數據呢?不能還手動 insert 吧,而且這個問題引起了我的好奇心 – 為什么會出現這種情況呢?
經過深入研究,終于找到了根本原因。本文記錄一下問題分析和解決過程,以供后續參考。
問題復現
使用新創建的賬號連接 DBeaver 進行數據導入操作:





導入過程中出現權限錯誤:

問題分析
理論上,DBeaver 導入 CSV 到表中只需要基本的 INSERT 權限。為了找出問題根源,我檢查了 DBeaver 導入失敗時的詳細錯誤日志:

通過分析異常堆棧(這里花費了不少時間),發現錯誤發生在:
SQLServerTableBase.enableIdentityInsert(SQLServerTableBase.java:266) SQLServerTableBase.beforeDataChange(SQLServerTableBase.java:253)
這說明 DBeaver 在導入數據前會嘗試執行 SET IDENTITY_INSERT ON 操作。具體執行的 SQL 語句應該是:
SET IDENTITY_INSERT WANGDS.dbo.zhangsan ON;
而 SET IDENTITY_INSERT 操作需要表的 ALTER 權限!這是 SQL Server 的安全機制,因為該操作會臨時改變表的行為,允許向 IDENTITY 列顯式插入值。
為什么會執行 IDENTITY_INSERT 操作?
檢查目標表的結構:
CREATE TABLE WANGDS.dbo.zhangsan (
id int IDENTITY(1,1) NOT NULL,
......
表中確實存在 IDENTITY 自增列 id int IDENTITY(1,1)!
當 DBeaver 檢測到目標表含有 IDENTITY 列時,其 SQLServer 適配器會預防性地啟用 IDENTITY_INSERT,以確保在 CSV 文件包含 ID 值時也能正確導入。
DBeaver 的完整導入流程如下:
SET IDENTITY_INSERT WANGDS.dbo.zhangsan ON;
-- 導入數據
SET IDENTITY_INSERT WANGDS.dbo.zhangsan OFF;
突然想起來之前看 CSV 文件的發現 ID 列怎么都是空值,這解釋了為什么 CSV 文件中的 ID 列都是空值,看來 CSV 文件是沒問題的。
問題驗證
為了驗證分析結果,手動執行以下 SQL:
SET IDENTITY_INSERT WANGDS.dbo.zhangsan ON;
執行結果確實報錯,錯誤信息與 DBeaver 的報錯一致:

解決方案
問題的解決方法很簡單,授予用戶對目標表的 ALTER 權限:
GRANT ALTER ON dbo.zhangsan TO LUCIFER;
執行該語句后,DBeaver 導入功能恢復正常。
排查過程中的實用 SQL
在問題排查過程中,以下 SQL 語句提供了重要的診斷信息:
-- 查找包含特定關鍵詞的最近執行語句,用于排查 IDENTITY_INSERT 相關操作
SELECT TOP 20
qt.text AS sql_text,
qs.execution_count,
qs.last_execution_time,
qs.total_elapsed_time,
qs.total_logical_reads
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) qt
WHERE qt.text LIKE '%zhangsan%'
OR qt.text LIKE '%IDENTITY_INSERT%'
ORDER BY qs.last_execution_time DESC;
-- 專門查找 ALTER 語句,用于確認是否有實際的表結構修改操作
SELECT TOP 10
qt.text AS sql_text,
qs.last_execution_time
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) qt
WHERE qt.text LIKE '%ALTER%'
AND qt.text LIKE '%zhangsan%'
ORDER BY qs.last_execution_time DESC;
-- 查看用戶所屬的數據庫角色,了解用戶通過角色繼承了哪些權限
SELECT
p.name AS principal_name,
p.type_desc AS principal_type,
r.name AS role_name
FROM sys.database_principals p
LEFT JOIN sys.database_role_members rm ON p.principal_id = rm.member_principal_id
LEFT JOIN sys.database_principals r ON rm.role_principal_id = r.principal_id
WHERE p.name = 'LUCIFER';
-- 查看用戶直接被授予的對象級權限,顯示用戶對特定對象(表、視圖等)的權限
SELECT
p.permission_name,
p.state_desc,
s.name AS schema_name,
o.name AS object_name
FROM sys.database_permissions p
LEFT JOIN sys.objects o ON p.major_id = o.object_id
LEFT JOIN sys.schemas s ON o.schema_id = s.schema_id
WHERE p.grantee_principal_id = USER_ID('LUCIFER')
ORDER BY s.name, o.name;
-- 查看用戶的所有有效權限(包括通過角色繼承的),這是最全面的權限檢查,包括直接權限和角色權限
SELECT
p.permission_name,
p.state_desc,
p.class_desc,
ISNULL(s.name, '') AS schema_name,
ISNULL(o.name, '') AS object_name
FROM sys.database_permissions p
LEFT JOIN sys.objects o ON p.major_id = o.object_id
LEFT JOIN sys.schemas s ON o.schema_id = s.schema_id
WHERE p.grantee_principal_id = USER_ID('LUCIFER')
OR p.grantee_principal_id IN (
SELECT role_principal_id
FROM sys.database_role_members
WHERE member_principal_id = USER_ID('LUCIFER')
)
ORDER BY p.permission_name, s.name, o.name;
當然還有很多授權的語句,就不一一列舉了!
寫在最后
在技術問題的排查過程中,表面現象往往只是冰山一角。本次問題看似是簡單的權限不足,但實際涉及到 DBeaver 內部的導入機制和 SQLServer 的安全策略。
通過這次問題的解決,不僅找到了根本原因,還深入了解了 DBeaver 的內部工作機制和 SQL Server 的權限管理體系。正如古語所言:“山重水復疑無路,柳暗花明又一村”,技術問題的解決過程雖然曲折,但最終的收獲往往超出預期。




