SQLite 的 System.AccessViolationException

Posted

技术标签:

【中文标题】SQLite 的 System.AccessViolationException【英文标题】:System.AccessViolationException with SQLite 【发布时间】:2015-01-13 02:18:53 【问题描述】:

现在,我正在处理这个错误:

An unhandled exception of type 'System.AccessViolationException' occurred in Unknown Module.

Additional information: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

它没有调用堆栈,因为没有为抛出它的 DLL 加载任何模块。我有一个应用程序枚举整个注册表并尝试将所有键/值保存到 SQLite 数据库文件中,作为注册表快照。从某种意义上说,如果它无法访问某些密钥,这些密钥将被丢弃,等等,它并不贪心:

using Microsoft.Win32;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data;
using System.Data.SQLite;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace RegistryMonitor

    class Program
    

        static void Main(string[] args)
        
            GenerateRegistrySnapshot("SnapshotOne.sqlite");
            Console.ReadLine();   
        

        static void GenerateRegistrySnapshot(string filename)
        
            File.Delete(filename);
            SQLiteConnection.CreateFile(filename);
            using (SQLiteConnection connection = new SQLiteConnection("Data Source=" + filename + ";Version=3;"))
            
                connection.Open();
                CreateTable(connection);
                Stopwatch watch = new Stopwatch();
                Console.WriteLine("Started walking the registry into file 0.", filename);
                watch.Start();
                transaction = connection.BeginTransaction();
                WalkTheRegistryAndPopulateTheSnapshot(connection);
                try
                
                    transaction.Commit();
                    transaction.Dispose();
                
                catch  
                Console.WriteLine("Finished walking the registry and populating the snapshot.");
                watch.Stop();
                Console.WriteLine("Finished walking the registry in 0 seconds.", watch.Elapsed.TotalSeconds);
                connection.Close();
            
        

        static void CreateTable(SQLiteConnection connection)
        
            SQLiteCommand command = new SQLiteCommand("CREATE TABLE Snapshot (ID INTEGER PRIMARY KEY AUTOINCREMENT, RegistryView INTEGER NULL, Path TEXT NULL, IsKey BOOLEAN NULL, RegistryValueKind INTEGER NULL, ValueName TEXT NULL, Value BLOB NULL, HashValue INTEGER NULL)", connection);
            command.ExecuteNonQuery();
        

        static SQLiteTransaction transaction = null;
        static int insertions = 0;
        static object transactionLock = new object();

        static void AddEntry(SQLiteConnection connection, RegistryPath path)
        
            SQLiteCommand command = new SQLiteCommand("INSERT INTO Snapshot (RegistryView, Path, IsKey, RegistryValueKind, ValueName, Value, HashValue) VALUES (@RegistryView, @Path, @IsKey, @RegistryValueKind, @ValueName, @Value, @HashValue)", connection);
            command.Parameters.Add("@RegistryView", DbType.Int32).Value = path.View;
            command.Parameters.Add("@Path", DbType.String).Value = path.Path;
            command.Parameters.Add("@IsKey", DbType.Boolean).Value = path.IsKey;
            command.Parameters.Add("@RegistryValueKind", DbType.Int32).Value = path.ValueKind;
            command.Parameters.Add("@ValueName", DbType.String).Value = path.ValueName;
            command.Parameters.Add("@Value", DbType.Object).Value = path.Value;
            command.Parameters.Add("@HashValue", DbType.Int32).Value = path.HashValue;
            command.ExecuteNonQuery();
            lock (transactionLock)
            
                insertions++;
                if (insertions > 100000)
                
                    insertions = 0;
                    transaction.Commit();
                    transaction.Dispose();
                    transaction = connection.BeginTransaction();
                
            
        

        private static void WalkTheRegistryAndPopulateTheSnapshot(SQLiteConnection connection)
        
            List<ManualResetEvent> handles = new List<ManualResetEvent>();
            foreach (RegistryHive hive in Enum.GetValues(typeof(RegistryHive)))
            
                foreach (RegistryView view in Enum.GetValues(typeof(RegistryView)).Cast<RegistryView>().ToList().Where(x => x != RegistryView.Default))
                
                    ManualResetEvent manualResetEvent = new ManualResetEvent(false);
                    handles.Add(manualResetEvent);
                    new Thread(() =>
                    
                        Console.WriteLine("Walking hive 0 in registry view 1.", hive.ToString(), view.ToString());
                        WalkKey(connection, view, RegistryKey.OpenBaseKey(hive, view));
                        Console.WriteLine("Finished walking hive 0 in registry view 1.", hive.ToString(), view.ToString());
                        manualResetEvent.Set();
                        Console.WriteLine("Finished setting event for hive 0 in registry view 1.", hive.ToString(), view.ToString());
                    ).Start();
                
            
            ManualResetEvent.WaitAll(handles.ToArray());
        

        private static void WalkKey(SQLiteConnection connection, RegistryView view, RegistryKey key)
        
            RegistryPath path = new RegistryPath(view, key.Name);
            AddEntry(connection, path);
            string[] valueNames = null;
            try
            
                valueNames = key.GetValueNames();
            
            catch  
            if (valueNames != null)
            
                foreach (string valueName in valueNames)
                
                    RegistryValueKind valueKind = RegistryValueKind.Unknown;
                    try
                    
                        valueKind = key.GetValueKind(valueName);
                    
                    catch  
                    object value = key.GetValue(valueName);
                    RegistryPath pathForValue = new RegistryPath(view, key.Name, valueKind, valueName, value);
                    AddEntry(connection, pathForValue);
                
            
            string[] subKeyNames = null;
            try
            
                subKeyNames = key.GetSubKeyNames();
            
            catch  
            if (subKeyNames != null)
            
                foreach (string subKeyName in subKeyNames)
                
                    try
                    
                        WalkKey(connection, view, key.OpenSubKey(subKeyName));
                    
                    catch  
                
            
        

        class RegistryPath
        
            public RegistryView View;
            public string Path;
            public bool IsKey;
            public RegistryValueKind ValueKind;
            public string ValueName;
            public object Value;
            public int HashValue;

            public RegistryPath(RegistryView view, string path)
            
                View = view;
                Path = path;
                IsKey = true;
                HashValue = (view.GetHashCode() ^ path.GetHashCode()).GetHashCode();
            

            public RegistryPath(RegistryView view, string path, RegistryValueKind valueKind, string valueName, object value)
            
                View = view;
                Path = path;
                IsKey = false;
                ValueKind = valueKind;
                ValueName = valueName;
                Value = value;
                if (value != null)
                
                    HashValue = (view.GetHashCode() ^ path.GetHashCode() ^ valueKind.GetHashCode() ^ valueName.GetHashCode() ^ value.GetHashCode()).GetHashCode();
                
                else
                
                    HashValue = (view.GetHashCode() ^ path.GetHashCode() ^ valueKind.GetHashCode() ^ valueName.GetHashCode()).GetHashCode();
                
            
        
    

由于应用程序是线程化的,AddEntry 使用围绕事务的并发性。最初,我没有使用插入计数器,但后来我很快意识到,由于我的应用程序是为 x86 构建并使用 .NET Framework 4.5.1,只要应用程序有将近 2GB 的 RAM,它就会完全冻结,导致我相信这是由于 x86 系统上的 SQLite 中的另一个问题,例如 x86 上 .NET 中集合的 2GB RAM 限制。我使用插入计数器来尝试经常提交事务,以免遇到大型事务队列。现在,即使我这样做了,我仍然留下了这个 AccessViolationException。我不确定是什么原因造成的。有没有人有任何线索?整个代码在这里,您可以将其复制并粘贴到控制台应用程序中,然后自己查看。我只是希望你有一个相当强大的注册表。非常感谢您的帮助;提前致谢!

【问题讨论】:

SQLite 没有 2 GB 的限制;任何不适合页面缓存的数据都会写入磁盘。您不能同时使用来自多个线程的连接。 等等,它不支持并发访问,还是说支持,只是每个线程需要自己的连接对象? 单个连接意味着单个事务。您不希望在同一个事务中混合来自多个线程的 SQL 语句。 我很困惑。那么,什么是实现并发和性能的好方法,并且仍然从多个线程工作,最终将数据贡献给我的数据库文件? SQLite 不允许并行写入。只需从单个线程编写。 (对于数据库,瓶颈很可能是 I/O。) 【参考方案1】:

CL. 值得称赞;他提到了SQLite can be safely used by multiple threads provided that no single database connection is used simultaneously in two or more threads。

这种方法解决了问题:

using Microsoft.Win32;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data;
using System.Data.SQLite;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace RegistryMonitor

    class Program
    

        static void Main(string[] args)
        
            GenerateRegistrySnapshot("SnapshotOne.sqlite");
            Console.ReadLine();   
        

        static void GenerateRegistrySnapshot(string filename)
        
            File.Delete(filename);
            SQLiteConnection.CreateFile(filename);
            bool finished = false;
            ConcurrentQueue<RegistryPath> queue = new ConcurrentQueue<RegistryPath>();
            Thread worker = new Thread(() =>
            
                using (SQLiteConnection connection = new SQLiteConnection("Data Source=" + filename + ";Version=3;"))
                
                    connection.Open();
                    CreateTable(connection);
                    SQLiteTransaction transaction = connection.BeginTransaction();
                    RegistryPath path;
                    while (!finished)
                    
                        while (queue.TryDequeue(out path))
                        
                            AddEntry(connection, path);
                        
                        Thread.Sleep(100);
                    
                    transaction.Commit();
                    transaction.Dispose();
                    connection.Close();
                
            );
            worker.Start();
            Stopwatch watch = new Stopwatch();
            Console.WriteLine("Started walking the registry into file 0.", filename);
            watch.Start();
            WalkTheRegistryAndPopulateTheSnapshot(queue);
            finished = true;
            worker.Join();
            watch.Stop();
            Console.WriteLine("Finished walking the registry in 0 seconds.", watch.Elapsed.TotalSeconds);
        

        static void CreateTable(SQLiteConnection connection)
        
            SQLiteCommand command = new SQLiteCommand("CREATE TABLE Snapshot (ID INTEGER PRIMARY KEY AUTOINCREMENT, RegistryView INTEGER NULL, Path TEXT NULL, IsKey BOOLEAN NULL, RegistryValueKind INTEGER NULL, ValueName TEXT NULL, Value BLOB NULL, HashValue INTEGER NULL)", connection);
            command.ExecuteNonQuery();
        

        static void AddEntry(SQLiteConnection connection, RegistryPath path)
        
            SQLiteCommand command = new SQLiteCommand("INSERT INTO Snapshot (RegistryView, Path, IsKey, RegistryValueKind, ValueName, Value, HashValue) VALUES (@RegistryView, @Path, @IsKey, @RegistryValueKind, @ValueName, @Value, @HashValue)", connection);
            command.Parameters.Add("@RegistryView", DbType.Int32).Value = path.View;
            command.Parameters.Add("@Path", DbType.String).Value = path.Path;
            command.Parameters.Add("@IsKey", DbType.Boolean).Value = path.IsKey;
            command.Parameters.Add("@RegistryValueKind", DbType.Int32).Value = path.ValueKind;
            command.Parameters.Add("@ValueName", DbType.String).Value = path.ValueName;
            command.Parameters.Add("@Value", DbType.Object).Value = path.Value;
            command.Parameters.Add("@HashValue", DbType.Int32).Value = path.HashValue;
            command.ExecuteNonQuery();
        

        private static void WalkTheRegistryAndPopulateTheSnapshot(ConcurrentQueue<RegistryPath> queue)
        
            List<ManualResetEvent> handles = new List<ManualResetEvent>();
            foreach (RegistryHive hive in Enum.GetValues(typeof(RegistryHive)))
            
                foreach (RegistryView view in Enum.GetValues(typeof(RegistryView)).Cast<RegistryView>().ToList().Where(x => x != RegistryView.Default))
                
                    ManualResetEvent manualResetEvent = new ManualResetEvent(false);
                    handles.Add(manualResetEvent);
                    new Thread(() =>
                    
                        WalkKey(queue, view, RegistryKey.OpenBaseKey(hive, view));
                        manualResetEvent.Set();
                    ).Start();
                
            
            ManualResetEvent.WaitAll(handles.ToArray());
        

        private static void WalkKey(ConcurrentQueue<RegistryPath> queue, RegistryView view, RegistryKey key)
        
            RegistryPath path = new RegistryPath(view, key.Name);
            queue.Enqueue(path);
            string[] valueNames = null;
            try
            
                valueNames = key.GetValueNames();
            
            catch  
            if (valueNames != null)
            
                foreach (string valueName in valueNames)
                
                    RegistryValueKind valueKind = RegistryValueKind.Unknown;
                    try
                    
                        valueKind = key.GetValueKind(valueName);
                    
                    catch  
                    object value = key.GetValue(valueName);
                    RegistryPath pathForValue = new RegistryPath(view, key.Name, valueKind, valueName, value);
                    queue.Enqueue(pathForValue);
                
            
            string[] subKeyNames = null;
            try
            
                subKeyNames = key.GetSubKeyNames();
            
            catch  
            if (subKeyNames != null)
            
                foreach (string subKeyName in subKeyNames)
                
                    try
                    
                        WalkKey(queue, view, key.OpenSubKey(subKeyName));
                    
                    catch  
                
            
        

        class RegistryPath
        
            public RegistryView View;
            public string Path;
            public bool IsKey;
            public RegistryValueKind ValueKind;
            public string ValueName;
            public object Value;
            public int HashValue;

            public RegistryPath(RegistryView view, string path)
            
                View = view;
                Path = path;
                IsKey = true;
                HashValue = (view.GetHashCode() ^ path.GetHashCode()).GetHashCode();
            

            public RegistryPath(RegistryView view, string path, RegistryValueKind valueKind, string valueName, object value)
            
                View = view;
                Path = path;
                IsKey = false;
                ValueKind = valueKind;
                ValueName = valueName;
                Value = value;
                if (value != null)
                
                    HashValue = (view.GetHashCode() ^ path.GetHashCode() ^ valueKind.GetHashCode() ^ valueName.GetHashCode() ^ value.GetHashCode()).GetHashCode();
                
                else
                
                    HashValue = (view.GetHashCode() ^ path.GetHashCode() ^ valueKind.GetHashCode() ^ valueName.GetHashCode()).GetHashCode();
                
            
        
    

编辑:甚至可能是这样的(非常少的内存使用)...

using Microsoft.Win32;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data;
using System.Data.SQLite;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace RegistryMonitor

    class Program
    

        static void Main(string[] args)
        
            GenerateRegistrySnapshot("Snapshot.sqlite");
            Console.ReadLine();   
        

        static object writeLock = new object();
        static AutoResetEvent writeReady = new AutoResetEvent(false);
        static AutoResetEvent writeCompleted = new AutoResetEvent(false);
        static RegistryPath pathToWrite;

        static void GenerateRegistrySnapshot(string filename)
        
            File.Delete(filename);
            SQLiteConnection.CreateFile(filename);
            bool finished = false;
            Thread worker = new Thread(() =>
            
                using (SQLiteConnection connection = new SQLiteConnection("Data Source=" + filename + ";Version=3;"))
                
                    connection.Open();
                    CreateTable(connection);
                    SQLiteTransaction transaction = connection.BeginTransaction();
                    while (!finished)
                    
                        writeReady.WaitOne();
                        if (finished)
                        
                            break;
                        
                        AddEntry(connection, pathToWrite);
                        writeCompleted.Set();
                    
                    transaction.Commit();
                    transaction.Dispose();
                    connection.Close();
                
            );
            worker.Start();
            Stopwatch watch = new Stopwatch();
            Console.WriteLine("Started walking the registry into file 0.", filename);
            watch.Start();
            WalkTheRegistryAndPopulateTheSnapshot();
            finished = true;
            writeReady.Set();
            worker.Join();
            watch.Stop();
            Console.WriteLine("Finished walking the registry in 0 seconds.", watch.Elapsed.TotalSeconds);
        

        static void CreateTable(SQLiteConnection connection)
        
            SQLiteCommand command = new SQLiteCommand("CREATE TABLE Snapshot (ID INTEGER PRIMARY KEY AUTOINCREMENT, RegistryView INTEGER NULL, Path TEXT NULL, IsKey BOOLEAN NULL, RegistryValueKind INTEGER NULL, ValueName TEXT NULL, Value BLOB NULL, HashValue INTEGER NULL)", connection);
            command.ExecuteNonQuery();
        

        static void AddEntry(SQLiteConnection connection, RegistryPath path)
        
            SQLiteCommand command = new SQLiteCommand("INSERT INTO Snapshot (RegistryView, Path, IsKey, RegistryValueKind, ValueName, Value, HashValue) VALUES (@RegistryView, @Path, @IsKey, @RegistryValueKind, @ValueName, @Value, @HashValue)", connection);
            command.Parameters.Add("@RegistryView", DbType.Int32).Value = path.View;
            command.Parameters.Add("@Path", DbType.String).Value = path.Path;
            command.Parameters.Add("@IsKey", DbType.Boolean).Value = path.IsKey;
            command.Parameters.Add("@RegistryValueKind", DbType.Int32).Value = path.ValueKind;
            command.Parameters.Add("@ValueName", DbType.String).Value = path.ValueName;
            command.Parameters.Add("@Value", DbType.Object).Value = path.Value;
            command.Parameters.Add("@HashValue", DbType.Int32).Value = path.HashValue;
            command.ExecuteNonQuery();
        

        private static void WalkTheRegistryAndPopulateTheSnapshot()
        
            List<ManualResetEvent> handles = new List<ManualResetEvent>();
            foreach (RegistryHive hive in Enum.GetValues(typeof(RegistryHive)))
            
                foreach (RegistryView view in Enum.GetValues(typeof(RegistryView)).Cast<RegistryView>().ToList().Where(x => x != RegistryView.Default))
                
                    ManualResetEvent manualResetEvent = new ManualResetEvent(false);
                    handles.Add(manualResetEvent);
                    new Thread(() =>
                    
                        WalkKey(view, RegistryKey.OpenBaseKey(hive, view));
                        manualResetEvent.Set();
                    ).Start();
                
            
            ManualResetEvent.WaitAll(handles.ToArray());
        

        private static void WalkKey(RegistryView view, RegistryKey key)
        
            RegistryPath path = new RegistryPath(view, key.Name);
            Write(path);
            string[] valueNames = null;
            try
            
                valueNames = key.GetValueNames();
            
            catch  
            if (valueNames != null)
            
                foreach (string valueName in valueNames)
                
                    RegistryValueKind valueKind = RegistryValueKind.Unknown;
                    try
                    
                        valueKind = key.GetValueKind(valueName);
                    
                    catch  
                    object value = key.GetValue(valueName);
                    RegistryPath pathForValue = new RegistryPath(view, key.Name, valueKind, valueName, value);
                    Write(pathForValue);
                
            
            string[] subKeyNames = null;
            try
            
                subKeyNames = key.GetSubKeyNames();
            
            catch  
            if (subKeyNames != null)
            
                foreach (string subKeyName in subKeyNames)
                
                    try
                    
                        WalkKey(view, key.OpenSubKey(subKeyName));
                    
                    catch  
                
            
        

        private static void Write(RegistryPath path)
        
            lock (writeLock)
            
                pathToWrite = path;
                writeReady.Set();
                writeCompleted.WaitOne();
            
        

        class RegistryPath
        
            public RegistryView View;
            public string Path;
            public bool IsKey;
            public RegistryValueKind ValueKind;
            public string ValueName;
            public object Value;
            public int HashValue;

            public RegistryPath(RegistryView view, string path)
            
                View = view;
                Path = path;
                IsKey = true;
                HashValue = (view.GetHashCode() ^ path.GetHashCode()).GetHashCode();
            

            public RegistryPath(RegistryView view, string path, RegistryValueKind valueKind, string valueName, object value)
            
                View = view;
                Path = path;
                IsKey = false;
                ValueKind = valueKind;
                ValueName = valueName;
                Value = value;
                if (value != null)
                
                    HashValue = (view.GetHashCode() ^ path.GetHashCode() ^ valueKind.GetHashCode() ^ valueName.GetHashCode() ^ value.GetHashCode()).GetHashCode();
                
                else
                
                    HashValue = (view.GetHashCode() ^ path.GetHashCode() ^ valueKind.GetHashCode() ^ valueName.GetHashCode()).GetHashCode();
                
            
        
    

【讨论】:

我很困惑。 Sqlite.org(通过您提供的链接)在默认的序列化模式下说“SQLite 可以不受限制地被多个线程安全使用”。是不是手动设置为多线程模式? @user3771957 “安全”意味着 SQLite 的内部数据结构不会被破坏。但是当多个线程在同一个连接/事务中执行它们的工作时,您的应用程序的逻辑很可能会崩溃。

以上是关于SQLite 的 System.AccessViolationException的主要内容,如果未能解决你的问题,请参考以下文章

SQLite 更新后:无法在 DLL 'SQLite.Interop.dll' 中找到名为 'sqlite3_changes_interop' 的入口点

SQLite 数据类型

在Ubuntu上升级SQLite,并让Python使用新版SQLite

SQLite的sqlite_sequence表

sqlite只有几条数据内存很大

玩转SQLite系列SQLite数据库应用案例实现历史搜索记录