두 테이블 간 조인을 할때 NOT IN 조건으로 JOIN 을 할 경우 처리하는 방법이다.

아래 예제 에서는 B 테이블의 value 컬럼의 내용을 NOT IN 조건으로 처리한다.

1. 

SELECT  A.*
FROM    A LEFT JOIN B ON A.value = B.value
WHERE   B.value IS NULL


2.

SELECT  A.*
FROM    A
WHERE   value NOT IN
(
SELECT  value
FROM    B
)

3.

SELECT  A.*
FROM    A
WHERE   NOT EXISTS
(
SELECT  value
FROM    B
WHERE   B.value = A.value
)


value 컬럼이 인덱스처리가 된 경우 성능차이는 없다고 한다.

그러나 value 컬럼이 NOT NULL임을 보장하지 않는 경우 하위 쿼리 결과 집합에 NULL 값이 있는지 여부에 따라 

다른 결과가 생성되므로 NOT IN 대신 LEFT JOIN / IS NULL 또는 NOT EXISTS를 사용해야한다.

참고

https://explainextended.com/2009/09/17/not-in-vs-not-exists-vs-left-join-is-null-oracle/

TEST 테이블의 A 컬럼에 아래와 같이 데이터가 있을 때

A


A 컬럼의 데이터를 모두 ,(콤마) 로 연결하여 하나의 데이터("가,나,다")로 나타내고 싶은 경우

아래 처럼 listagg 를 이용해 쿼리하면 된다.


SELECT LISTAGG(A,' ,') RESULT FROM TEST


결과>

RESULT

가,나,다



select trim(regexp_substr('a,b,c','[^,]+',1,LEVEL)) item_id,

trim(regexp_substr('1,2,3','[^,]+',1,LEVEL)) item_value

from dual

connect by level < 4


결과>

 item_id

 item_value

 a

 1

 b

 2

 c

 3



https://www.tutorialspoint.com/sqlite/index.htm

사용법과 설명등이 잘되어있다.

 

 

SQLite Bulk Insert In C#/.NET

Recently, I had a project where I needed to load 1 million+ records into a SQLite database. I downloaded the SQLite ADO.NET adapter and setup the Entity framework to map to my SQLite database. All was simple and all was well!

I started inserting the data into my database; lo and behold, it was taking forever! In fact, it took most of a full working day to insert my data. I knew something wasn't right. A simple Google search pointed me to the SQLite FAQ. It turns out that SQLite wraps every INSERT into a transaction. Simple solution: start a transaction and perform multiple INSERTs. I needed my inserts to be fast, so I just had to write a class to encapsulate this.

It's very simple to use. Make sure that your SQLite database file has been created and your table has been created as well.

Let's assume your database schema looks like the following: Id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, LastName VARCHAR(16) NOT NULL, Height REAL NOT NULL

You would then use SQLiteBulkInsert as follows:

SQLiteBulkInsert sbi = new SQLiteBulkInsert(yourDatabaseConnectionObject, "yourTableName");
sbi.AddParameter("LastName", DbType.String);
sbi.AddParameter("Height", DbType.Single);

You can then insert records:

for (int x = 0; x &lt; 10000; x++)
    sbi.Insert(new object[]{someString, someFloat});
sbi.Flush();

That's it! You should look at the file SQLiteBulkInsertTest.cs for more details. SQLiteBulkInsert.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SQLite;
using System.Data;

namespace YourProject.Data.SQLite
{
    public class SQLiteBulkInsert
    {
        private SQLiteConnection m_dbCon;
        private SQLiteCommand m_cmd;
        private SQLiteTransaction m_trans;

        private Dictionary m_parameters = new Dictionary();

        private uint m_counter = 0;

        private string m_beginInsertText;

        public SQLiteBulkInsert(SQLiteConnection dbConnection, string tableName) {
            m_dbCon = dbConnection;
            m_tableName = tableName;

            StringBuilder query = new StringBuilder(255);
            query.Append("INSERT INTO ["); query.Append(tableName); query.Append("] (");
            m_beginInsertText = query.ToString();
        }

        private bool m_allowBulkInsert = true;
        public bool AllowBulkInsert { get { return m_allowBulkInsert; } set { m_allowBulkInsert = value; } }

        public string CommandText {
            get {
                if (m_parameters.Count &lt; 1)
                    throw new SQLiteException("You must add at least one parameter.");

                StringBuilder sb = new StringBuilder(255);
                sb.Append(m_beginInsertText);

                foreach (string param in m_parameters.Keys) {
                    sb.Append('[');
                    sb.Append(param);
                    sb.Append(']');
                    sb.Append(", ");
                }
                sb.Remove(sb.Length - 2, 2);

                sb.Append(") VALUES (");

                foreach (string param in m_parameters.Keys) {
                    sb.Append(m_paramDelim);
                    sb.Append(param);
                    sb.Append(", ");
                }
                sb.Remove(sb.Length - 2, 2);

                sb.Append(")");

                return sb.ToString();
            }
        }

        private uint m_commitMax = 10000;
        public uint CommitMax { get { return m_commitMax; } set { m_commitMax = value; } }

        private string m_tableName;
        public string TableName { get { return m_tableName; } }

        private string m_paramDelim = ":";
        public string ParamDelimiter { get { return m_paramDelim; } }

        public void AddParameter(string name, DbType dbType) {
            SQLiteParameter param = new SQLiteParameter(m_paramDelim + name, dbType);
            m_parameters.Add(name, param);
        }

        public void Flush() {
            try {
                if (m_trans != null)
                    m_trans.Commit();
            }
            catch (Exception ex) { throw new Exception("Could not commit transaction. See InnerException for more details", ex); }
            finally {
                if (m_trans != null)
                    m_trans.Dispose();

                m_trans = null;
                m_counter = 0;
            }
        }

        public void Insert(object[] paramValues) {
            if (paramValues.Length != m_parameters.Count)
                throw new Exception("The values array count must be equal to the count of the number of parameters.");

            m_counter++;

            if (m_counter == 1) {
                if (m_allowBulkInsert)
                    m_trans = m_dbCon.BeginTransaction();

                m_cmd = m_dbCon.CreateCommand();
                foreach (SQLiteParameter par in m_parameters.Values)
                    m_cmd.Parameters.Add(par);

                m_cmd.CommandText = this.CommandText;
            }

            int i = 0;
            foreach (SQLiteParameter par in m_parameters.Values) {
                par.Value = paramValues[i];
                i++;
            }

            m_cmd.ExecuteNonQuery();

            if (m_counter == m_commitMax) {
                try {
                    if (m_trans != null)
                        m_trans.Commit();
                }
                catch (Exception ex) { }
                finally {
                    if (m_trans != null) {
                        m_trans.Dispose();
                        m_trans = null;
                    }

                    m_counter = 0;
                }
            }
        }
    }
}

SQLiteBulkInsertTest.cs:

using YourProject.Data.SQLite;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Data.SQLite;
using System.Data;
using System.IO;
using System;

namespace TestYourProject
{
    /// 
    ///This is a test class for SQLiteBulkInsertTest and is intended
    ///to contain all SQLiteBulkInsertTest Unit Tests
    ///
    [TestClass()]
    public class SQLiteBulkInsertTest
    {
        private static string m_testDir;
        private static string m_testFile;
        private static string m_testTableName = "test_table";
        private static string m_connectionString;

        private static string m_deleteAllQuery = "DELETE FROM [{0}]";
        private static string m_countAllQuery = "SELECT COUNT(id) FROM [{0}]";
        private static string m_selectAllQuery = "SELECT * FROM [{0}]";

        private static SQLiteConnection m_dbCon;
        private static SQLiteCommand m_deleteAllCmd;
        private static SQLiteCommand m_countAllCmd;
        private static SQLiteCommand m_selectAllCmd;

        private TestContext testContextInstance;

        /// 
        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///
        public TestContext TestContext {
            get {
                return testContextInstance;
            }
            set {
                testContextInstance = value;
            }
        }

        #region Additional test attributes
        //
        //You can use the following additional attributes as you write your tests:
        //
        //Use ClassInitialize to run code before running the first test in the class
        [ClassInitialize()]
        public static void MyClassInitialize(TestContext testContext){
            Random rand = new Random(Environment.TickCount);
            int rn = rand.Next(0, int.MaxValue);
            m_testDir = @"C:\SqliteBulkInsertTest-" + rn + @"\";
            m_testFile = m_testDir + "db.sqlite";

            if (!Directory.Exists(m_testDir))
                Directory.CreateDirectory(m_testDir);

            if (!File.Exists(m_testFile)) {
                FileStream fs = File.Create(m_testFile);
                fs.Close();
            }

            m_connectionString = string.Format(@"data source={0};datetimeformat=Ticks", m_testFile);
            m_dbCon = new SQLiteConnection(m_connectionString);
            m_dbCon.Open();

            SQLiteCommand cmd = m_dbCon.CreateCommand();
            string query = "CREATE TABLE IF NOT EXISTS [{0}] (id INTEGER PRIMARY KEY AUTOINCREMENT, somestring VARCHAR(16), somereal REAL, someint INTEGER(4), somedt DATETIME)";
            query = string.Format(query, m_testTableName);
            cmd.CommandText = query;
            cmd.ExecuteNonQuery();
        }
        //
        //Use ClassCleanup to run code after all tests in a class have run
        [ClassCleanup()]
        public static void MyClassCleanup(){
            m_dbCon.Close();

            File.Delete(m_testFile);
            Directory.Delete(m_testDir);
        }
        #endregion

        private void AddParameters(SQLiteBulkInsert target) {
            target.AddParameter("somestring", DbType.String);
            target.AddParameter("somereal", DbType.String);
            target.AddParameter("someint", DbType.Int32);
            target.AddParameter("somedt", DbType.DateTime);
        }

        private long CountRecords() {
            m_countAllCmd = m_dbCon.CreateCommand();
            m_countAllCmd.CommandText = string.Format(m_countAllQuery, m_testTableName);

            long ret = (long)m_countAllCmd.ExecuteScalar();
            m_countAllCmd.Dispose();

            return ret;
        }

        private void DeleteRecords() {
            m_deleteAllCmd = m_dbCon.CreateCommand();
            m_deleteAllCmd.CommandText = string.Format(m_deleteAllQuery, m_testTableName);

            m_deleteAllCmd.ExecuteNonQuery();
            m_deleteAllCmd.Dispose();
        }

        private SQLiteDataReader SelectAllRecords() {
            m_selectAllCmd = m_dbCon.CreateCommand();
            m_selectAllCmd.CommandText = string.Format(m_selectAllQuery, m_testTableName);
            return m_selectAllCmd.ExecuteReader();
        }

        [TestMethod()]
        public void AddParameterTest() {
            SQLiteBulkInsert target = new SQLiteBulkInsert(m_dbCon, m_testTableName);
            AddParameters(target);

            string pd = target.ParamDelimiter;
            string expectedStmnt = "INSERT INTO [{0}] ([somestring], [somereal], [someint], [somedt]) VALUES ({1}somestring, {2}somereal, {3}someint, {4}somedt)";
            expectedStmnt = string.Format(expectedStmnt, m_testTableName, pd, pd, pd, pd);
            Assert.AreEqual(expectedStmnt, target.CommandText);
        }

        [TestMethod()]
        public void SQLiteBulkInsertConstructorTest() {
            SQLiteBulkInsert target = new SQLiteBulkInsert(m_dbCon, m_testTableName);
            Assert.AreEqual(m_testTableName, target.TableName);

            bool wasException = false;
            try {
                string a = target.CommandText;
            }
            catch (SQLiteException ex) { wasException = true; }

            Assert.IsTrue(wasException);
        }

        [TestMethod()]
        public void CommandTextTest() {
            SQLiteBulkInsert target = new SQLiteBulkInsert(m_dbCon, m_testTableName);
            AddParameters(target);

            string pd = target.ParamDelimiter;
            string expectedStmnt = "INSERT INTO [{0}] ([somestring], [somereal], [someint], [somedt]) VALUES ({1}somestring, {2}somereal, {3}someint, {4}somedt)";
            expectedStmnt = string.Format(expectedStmnt, m_testTableName, pd, pd, pd, pd);
            Assert.AreEqual(expectedStmnt, target.CommandText);
        }

        [TestMethod()]
        public void TableNameTest() {
            SQLiteBulkInsert target = new SQLiteBulkInsert(m_dbCon, m_testTableName);
            Assert.AreEqual(m_testTableName, target.TableName);
        }

        [TestMethod()]
        public void InsertTest() {
            SQLiteBulkInsert target = new SQLiteBulkInsert(m_dbCon, m_testTableName);

            bool didThrow = false;
            try {
                target.Insert(new object[] { "hello" }); //object.length must equal the number of parameters added
            }
            catch (Exception ex) { didThrow = true; }
            Assert.IsTrue(didThrow);

            AddParameters(target);

            target.CommitMax = 4;
            DateTime dt1 = DateTime.Now; DateTime dt2 = DateTime.Now; DateTime dt3 = DateTime.Now; DateTime dt4 = DateTime.Now;
            target.Insert(new object[] { "john", 3.45f, 10, dt1 });
            target.Insert(new object[] { "paul", -0.34f, 100, dt2 });
            target.Insert(new object[] { "ringo", 1000.98f, 1000, dt3 });
            target.Insert(new object[] { "george", 5.0f, 10000, dt4 });

            long count = CountRecords();
            Assert.AreEqual(4, count);

            SQLiteDataReader reader = SelectAllRecords();

            Assert.IsTrue(reader.Read());
            Assert.AreEqual("john", reader.GetString(1)); Assert.AreEqual(3.45f, reader.GetFloat(2));
            Assert.AreEqual(10, reader.GetInt32(3)); Assert.AreEqual(dt1, reader.GetDateTime(4));

            Assert.IsTrue(reader.Read());
            Assert.AreEqual("paul", reader.GetString(1)); Assert.AreEqual(-0.34f, reader.GetFloat(2));
            Assert.AreEqual(100, reader.GetInt32(3)); Assert.AreEqual(dt2, reader.GetDateTime(4));

            Assert.IsTrue(reader.Read());
            Assert.AreEqual("ringo", reader.GetString(1)); Assert.AreEqual(1000.98f, reader.GetFloat(2));
            Assert.AreEqual(1000, reader.GetInt32(3)); Assert.AreEqual(dt3, reader.GetDateTime(4));

            Assert.IsTrue(reader.Read());
            Assert.AreEqual("george", reader.GetString(1)); Assert.AreEqual(5.0f, reader.GetFloat(2));
            Assert.AreEqual(10000, reader.GetInt32(3)); Assert.AreEqual(dt4, reader.GetDateTime(4));

            Assert.IsFalse(reader.Read());

            DeleteRecords();

            count = CountRecords();
            Assert.AreEqual(0, count);
        }

        [TestMethod()]
        public void FlushTest() {
            string[] names = new string[] { "metalica", "beatles", "coldplay", "tiesto", "t-pain", "blink 182", "plain white ts", "staind", "pink floyd" };
            Random rand = new Random(Environment.TickCount);

            SQLiteBulkInsert target = new SQLiteBulkInsert(m_dbCon, m_testTableName);
            AddParameters(target);

            target.CommitMax = 1000;

            //Insert less records than commitmax
            for (int x = 0; x &lt; 50; x++)
                target.Insert(new object[] { names[rand.Next(names.Length)], (float)rand.NextDouble(), rand.Next(50), DateTime.Now });

            //Close connect to verify records were not inserted
            m_dbCon.Close();

            m_dbCon = new SQLiteConnection(m_connectionString);
            m_dbCon.Open();

            long count = CountRecords();
            Assert.AreEqual(0, count);

            //Now actually verify flush worked
            target = new SQLiteBulkInsert(m_dbCon, m_testTableName);
            AddParameters(target);

            target.CommitMax = 1000;

            //Insert less records than commitmax
            for (int x = 0; x &lt; 50; x++)
                target.Insert(new object[] { names[rand.Next(names.Length)], (float)rand.NextDouble(), rand.Next(50), DateTime.Now });

            target.Flush();

            count = CountRecords();
            Assert.AreEqual(50, count);

            //Close connect to verify flush worked
            m_dbCon.Close();

            m_dbCon = new SQLiteConnection(m_connectionString);
            m_dbCon.Open();

            count = CountRecords();
            Assert.AreEqual(50, count);

            DeleteRecords();
            count = CountRecords();
            Assert.AreEqual(0, count);
        }

        [TestMethod()]
        public void CommitMaxTest() {
            SQLiteBulkInsert target = new SQLiteBulkInsert(m_dbCon, m_testTableName);

            target.CommitMax = 4;
            Assert.AreEqual(4, target.CommitMax);

            target.CommitMax = 1000;
            Assert.AreEqual(1000, target.CommitMax);
        }

        //SPEED TEST
        [TestMethod()]
        public void AllowBulkInsertTest() {
            string[] names = new string[] { "metalica", "beatles", "coldplay", "tiesto", "t-pain", "blink 182", "plain white ts", "staind", "pink floyd"};
            Random rand = new Random(Environment.TickCount);

            SQLiteBulkInsert target = new SQLiteBulkInsert(m_dbCon, m_testTableName);
            AddParameters(target);

            const int COUNT = 100;

            target.CommitMax = COUNT;

            DateTime start1 = DateTime.Now;
            for (int x = 0; x &lt; COUNT; x++)
                target.Insert(new object[] { names[rand.Next(names.Length)], (float)rand.NextDouble(), rand.Next(COUNT), DateTime.Now });

            DateTime end1 = DateTime.Now;
            TimeSpan delta1 = end1 - start1;

            DeleteRecords();

            target.AllowBulkInsert = false;
            DateTime start2 = DateTime.Now;
            for (int x = 0; x &lt; COUNT; x++)
                target.Insert(new object[] { names[rand.Next(names.Length)], (float)rand.NextDouble(), rand.Next(COUNT), DateTime.Now });

            DateTime end2 = DateTime.Now;
            TimeSpan delta2 = end2 - start2;

            //THIS MAY FAIL DEPENDING UPON THE MACHINE THE TEST IS RUNNING ON.
            Assert.IsTrue(delta1.TotalSeconds &lt; 0.1); //approx true for 100 recs             Assert.IsTrue(delta2.TotalSeconds &gt; 1.0); //approx true for 100 recs;

            //UNCOMMENT THIS TO MAKE IT FAIL AND SEE ACTUAL NUMBERS IN FAILED REPORT
            //Assert.AreEqual(delta1.TotalSeconds, delta2.TotalSeconds);

            DeleteRecords();
        }
    }
}

 출처 : http://procbits.com/2009/09/08/sqlite-bulk-insert

'DB' 카테고리의 다른 글

[Oracle] 오라클 문자열 잘라내기  (0) 2020.05.04
(SQLite) SQLite 공부하기 좋은 사이트  (0) 2018.03.07
SQLite Bulk Insert  (0) 2018.03.07
(SQLite) 속도향상 - 링크  (0) 2018.03.01
(Oracle) UNPIVOT  (1) 2018.02.20
(SQLite) insert 쿼리 수행 속도 높이기  (3) 2018.01.20

https://medium.com/@JasonWyatt/squeezing-performance-from-sqlite-insertions-971aff98eef2

 

http://blog.quibb.org/2010/08/fast-bulk-inserts-into-sqlite/

 

https://blogs.msdn.microsoft.com/andy_wigley/2013/11/21/how-to-massively-improve-sqlite-performance-using-sqlwinrt/

 

https://www.sqlite.org/fasterthanfs.html

 

* litedb

http://www.litedb.org/

 

* flat file database system

http://www.c-sharpcorner.com/UploadFile/e731e4/flatfile-database-system/

'DB' 카테고리의 다른 글

(SQLite) SQLite 공부하기 좋은 사이트  (0) 2018.03.07
SQLite Bulk Insert  (0) 2018.03.07
(SQLite) 속도향상 - 링크  (0) 2018.03.01
(Oracle) UNPIVOT  (1) 2018.02.20
(SQLite) insert 쿼리 수행 속도 높이기  (3) 2018.01.20
SQLite 참고 링크 모음  (1) 2017.11.23

Oracle 11g 부터 제공되는 UNPIVOT 명령어가 있는데

일일히 UNPIVOT 쿼리를 만들어 내지 않고 쉽게 UNPIVOT 을 할수 있다.

그런데 속도가 느리다 ㅜㅠ

하여 찾아보니 아래처럼 하면 속도가 10배나 빨라진다고 하는데..

한번 시도해 봐야겠다.

 

WITH base as (select my_data, ...) select * from base UNPIVOT (...)

'DB' 카테고리의 다른 글

SQLite Bulk Insert  (0) 2018.03.07
(SQLite) 속도향상 - 링크  (0) 2018.03.01
(Oracle) UNPIVOT  (1) 2018.02.20
(SQLite) insert 쿼리 수행 속도 높이기  (3) 2018.01.20
SQLite 참고 링크 모음  (1) 2017.11.23
SQLite 공유 캐시 모드  (0) 2017.08.24
  1. Favicon of https://kjun.kr keisoft kjun.kr 2018.03.05 12:55 신고

    속도가 10배이상 빨라졌다 위 방법을 추천한다

1. transaction 으로 묶어서 처리한다.

2. cash_size 값 변경 (예:1000)

3. page_size 값 변경 (예:2048)

4. journal_mode=wal

5. temp_store=memory

6. synchronous=off

  1. Favicon of https://kjun.kr keisoft kjun.kr 2018.03.07 14:51 신고

    https://www.tutorialspoint.com/sqlite/sqlite_c_cpp.htm

  2. Favicon of https://kjun.kr keisoft kjun.kr 2018.03.07 14:52 신고

    https://github.com/SRombauts/SQLiteCpp

  3. Favicon of https://kjun.kr keisoft kjun.kr 2018.03.07 14:53 신고

    http://igotit.tistory.com/entry/Visual-C-%EC%97%90%EC%84%9C-SQLite-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%ED%99%9C%EC%9A%A9%ED%95%98%EA%B8%B0

http://gywn.net/category/sqlite/
http://egloos.zum.com/ysbulpe/v/2282868
https://www.codeproject.com/Articles/746191/SQLite-Helper-Csharp
https://stackoverflow.com/questions/5367118/how-perform-sqlite-query-with-a-data-reader-without-locking-database
http://www.sqlite.org/wal.html
http://nocode.tistory.com/95
http://aroundck.tistory.com/3032
http://hooonko.tumblr.com/post/15392179011/sqlite-write-ahead-logging-%EB%B2%88%EC%97%AD
http://www.sqlite.org/pragma.html#pragma_journal_size_limit
https://blog.xojo.com/2015/12/21/speed-up-sqlite-with-write-ahead-logging/
https://stackoverflow.com/questions/31971905/enable-shared-cache-mode-in-system-data-sqlite-net
http://frontier.snucse.org/sqlite_faster.html
http://blog.naver.com/goddes4/220326357700
http://www.w3ii.com/ko/sqlite/sqlite_detach_database.html
https://sqlite.org/limits.html
https://stackoverflow.com/questions/19479253/how-to-get-row-number-in-sqlite
https://stackoverflow.com/questions/1237068/pivot-in-sqlite
http://www.sqlite.org/limits.html
http://www.sqlite.org/pragma.html#pragma_schema_version

'DB' 카테고리의 다른 글

(Oracle) UNPIVOT  (1) 2018.02.20
(SQLite) insert 쿼리 수행 속도 높이기  (3) 2018.01.20
SQLite 참고 링크 모음  (1) 2017.11.23
SQLite 공유 캐시 모드  (0) 2017.08.24
[Oracle] 시간 타입 컬럼 시분초 형태로 결과보기 (nls_date)  (0) 2017.06.28
ORACLE vs DB2  (0) 2017.05.30
  1. Favicon of https://kjun.kr keisoft kjun.kr 2017.11.24 13:48 신고

    https://ourcodeworld.com/articles/read/366/how-to-generate-a-pdf-from-html-using-wkhtmltopdf-with-c-in-winforms

http://www.sqlite.org/sharedcache.html

번역

 

 1. SQLite 공유 캐시 모드

 버전 3.3.0 (2006-01-11)부터 SQLite는 임베디드 서버에서 사용하기위한 특별한 "공유 캐시"모드 (기본적으로 비활성화 됨)를 포함합니다.  공유 캐시 모드가 사용 가능하고 스레드가 동일한 데이터베이스에 대한 다중 연결을 설정하면 연결은 단일 데이터 및 스키마 캐시를 공유합니다.  이렇게하면 시스템에 필요한 메모리 및 IO의 양을 크게 줄일 수 있습니다.

 버전 3.5.0 (2007-09-04)에서는 공유 캐시 모드가 수정되어 단일 스레드 내에서가 아닌 전체 프로세스에서 동일한 캐시를 공유 할 수 있습니다.  이 변경 이전에는 스레드간에 데이터베이스 연결을 전달하는 데 제한이있었습니다.  이러한 제한 사항은 3.5.0 업데이트에서 제외되었습니다.  이 문서는 3.5.0 버전의 공유 캐시 모드에 대해 설명합니다.

 공유 캐시 모드는 경우에 따라 잠금 모델의 의미를 변경합니다.  자세한 내용은이 문서에서 설명합니다.  일반적인 SQLite 잠금 모델에 대한 기본적인 이해가 있습니다 (자세한 내용은 SQLite 버전 3의 파일 잠금 및 동시성 참조).

 2. 공유 캐시 잠금 모델

 외부 적으로는 다른 프로세스 또는 스레드의 관점에서 볼 때 공유 캐시를 사용하는 둘 이상의 데이터베이스 연결 이 단일 연결로 나타납니다.  여러 공유 캐시 또는 일반 데이터베이스 사용자간에 중재하는 데 사용되는 잠금 프로토콜은 다른 곳에서 설명합니다.

 

 그림 1

 그림 1은 세 개의 데이터베이스 연결이 설정된 런타임 구성 예제를 보여줍니다.  연결 1은 일반적인 SQLite 데이터베이스 연결입니다.  연결 2와 3은 캐시를 공유합니다. 일반 잠금 프로토콜은 연결 1과 공유 캐시 간의 데이터베이스 액세스를 직렬화하는 데 사용됩니다.  연결 2와 3에 의한 공유 캐시에 대한 직렬화 (또는 아래의 "읽기 - 미 확정적 격리 모드"참조)에 사용되는 내부 프로토콜은이 절의 나머지 부분에서 설명합니다.

 공유 캐시 잠금 모델, 트랜잭션 수준 잠금, 테이블 수준 잠금 및 스키마 수준 잠금의 세 가지 수준이 있습니다.  그것들은 다음의 세 가지 하위 섹션에서 설명합니다.

 2.1.  트랜잭션 수준 잠금

 SQLite 연결은 두 종류의 트랜잭션, 읽기 및 쓰기 트랜잭션을 열 수 있습니다.  이것은 명시 적으로 수행되지 않고 트랜잭션은 암시 적으로 데이터베이스 트랜잭션 테이블에 쓰기 전까지는 암시 적으로 읽기 트랜잭션입니다.이 시점에서 트랜잭션은 쓰기 트랜잭션이됩니다.

 최대 하나의 공유 캐시에 대한 연결은 한 번에 쓰기 트랜잭션을 열 수 있습니다.  이것은 임의의 수의 읽기 트랜잭션과 공존 할 수 있습니다.

 2.2.  테이블 수준 잠금

 둘 이상의 연결에서 공유 캐시를 사용하는 경우 잠금은 테이블 당 동시 액세스 시도를 직렬화하는 데 사용됩니다.  테이블은 두 가지 유형의 잠금 "읽기 잠금"과 "쓰기 잠금"을 지원합니다.  잠금은 연결에 부여됩니다. 각 데이터베이스 연결에는 읽기 잠금, 쓰기 잠금 또는 각 데이터베이스 테이블에 대한 잠금이 없습니다.

 한 번에 하나의 테이블에 여러 개의 활성 읽기 잠금 또는 단일 활성 쓰기 잠금이있을 수 있습니다.  데이터를 테이블로 읽으려면 먼저 연결에서 읽기 잠금을 얻어야합니다.  테이블에 쓰려면 연결에서 해당 테이블에 대한 쓰기 잠금을 얻어야합니다.  필요한 테이블 잠금을 얻을 수 없으면 쿼리가 실패하고 SQLITE_LOCKED가 호출자에게 반환됩니다.

 일단 연결이 테이블 잠금을 얻으면 현재 트랜잭션 (읽기 또는 쓰기)이 끝날 때까지 해제되지 않습니다.

 2.2.1.  읽기 커밋되지 않은 격리 모드

 위에서 설명한 동작은 read_uncommitted pragma를 사용하여 격리 수준을 serialize (기본값)에서 read-uncommitted로 변경하여 약간 수정할 수 있습니다.

 읽기 - 확약되지 않은 모드의 데이터베이스 연결은 위에서 설명한대로 데이터베이스 테이블에서 읽기 전에 읽기 잠금을 얻으려고 시도하지 않습니다.  이로 인해 다른 데이터베이스 연결이 읽는 동안 테이블을 수정하더라도 일관성없는 쿼리 결과가 발생할 수 있지만 읽기 연결되지 않은 모드에서 연결에 의해 열린 읽기 트랜잭션이 다른 연결에 의해 차단되거나 차단되지 않을 수도 있습니다.

 읽기 - 커밋되지 않은 모드는 데이터베이스 테이블에 쓰는 데 필요한 잠금에 영향을주지 않습니다 (즉, 읽기 - 커밋되지 않은 연결은 쓰기 잠금을 가져야하므로 데이터베이스 쓰기는 여전히 차단되거나 차단 될 수 있음).  또한 read-uncommitted 모드는 아래 열거 된 규칙 ( "스키마 (sqlite_master) 레벨 잠금"절 참조)에서 요구하는 sqlite_master 잠금에 영향을 미치지 않습니다.

   / * read-uncommitted 플래그의 값을 설정합니다 :
   **
   ** True -> 읽기 - 커밋되지 않은 모드로 연결을 설정합니다.
   ** 거짓 -> 연결을 직렬화 (기본값) 모드로 설정하십시오.
   * /
   PRAGMA read_uncommitted = <boolean>;

   / * read-uncommitted 플래그의 현재 값 검색 * /
   PRAGMA read_uncommitted;


 2.3.  스키마 (sqlite_master) 수준 잠금

 sqlite_master 테이블은 다른 모든 데이터베이스 테이블과 동일한 방식으로 공유 캐시 읽기 및 쓰기 잠금을 지원합니다 (위의 설명 참조).  다음 특별 규정이 적용됩니다.
•  연결은 데이터베이스 테이블에 액세스하거나 다른 읽기 또는 쓰기 잠금을 얻기 전에 sqlite_master 에서 읽기 잠금을 가져와야 합니다.
•  데이터베이스 스키마 (예 : CREATE 또는 DROP TABLE 문)를 수정하는 명령문을 실행하기 전에 sqlite_master 에서 쓰기 잠금을 가져와야 합니다.
•  다른 연결에서 연결된 데이터베이스 (기본 데이터베이스 "main"포함)의 sqlite_master 테이블에 쓰기 잠금이있는 경우 연결에서 SQL 문을 컴파일하지 못할 수 있습니다.

 3. 스레드 관련 문제

 SQLite 버전 3.3.0에서 3.4.2까지 공유 캐시 모드를 사용하는 경우 sqlite3_open () 을 호출 한 스레드에서만 데이터베이스 연결을 사용할 수 있습니다.  그리고 연결은 동일한 스레드의 다른 연결과 캐시를 공유 할 수있었습니다.  이러한 제한 사항은 SQLite 버전 3.5.0 (2007-09-04)부터 삭제되었습니다.

 4. 공유 캐시 및 가상 테이블

 이전 버전의 SQLite에서는 공유 캐시 모드를 가상 테이블과 함께 사용할 수 없었습니다.  이 제한 사항은 SQLite 버전 3.6.17 (2009-08-10)에서 제거되었습니다.

 5. 공유 캐시 모드 활성화

 공유 캐시 모드는 프로세스별로 사용할 수 있습니다.  C 인터페이스를 사용하여 다음 API를 사용하여 공유 캐시 모드를 전역 적으로 활성화 또는 비활성화 할 수 있습니다.

 int sqlite3_enable_shared_cache (int);


 sqlite3_enable_shared_cache ()를 호출 할 때마다 sqlite3_open () , sqlite3_open16 () 또는 sqlite3_open_v2 ()를 사용하여 생성 된 후속 데이터베이스 연결에 영향을줍니다.  이미 존재하는 데이터베이스 연결은 영향을받지 않습니다.  sqlite3_enable_shared_cache ()를 호출 할 때마다 동일한 프로세스 내의 모든 이전 호출이 무시됩니다.

 sqlite3_open_v2 ()를 사용하여 생성 된 개별 데이터베이스 연결은 세 번째 매개 변수 인 SQLITE_OPEN_SHAREDCACHE 또는 SQLITE_OPEN_PRIVATECACHE 플래그를 사용하여 공유 캐시 모드에 참여하거나 참여하지 않도록 선택할 수 있습니다.  이러한 플래그 중 하나를 사용하면 sqlite3_enable_shared_cache ()가 설정 한 전역 공유 캐시 모드 설정이 무시됩니다.  플래그 중 하나만 사용해야합니다.  sqlite3_open_v2 () 의 세 번째 인수에서 SQLITE_OPEN_SHAREDCACHE 및 SQLITE_OPEN_PRIVATECACHE 플래그가 모두 사용되는 경우 동작은 정의되지 않습니다.

 URI 파일 이름 이 사용되면 "cache"쿼리 매개 변수를 사용하여 데이터베이스에서 공유 캐시를 사용할지 여부를 지정할 수 있습니다.  공유 캐시를 사용하려면 "cache = shared"를 사용하고 공유 캐시를 사용하지 않으려면 "cache = private"을 사용하십시오.  URI 쿼리 매개 변수를 사용하여 데이터베이스 연결의 캐시 공유 동작을 지정하는 기능을 사용하면 ATTACH 문에서 캐시 공유를 제어 할 수 있습니다.  예 :

 ATTACH의 파일 : aux.db? cache = shared 'aux;


  6. 공유 캐시 및 메모리 데이터베이스

 SQLite 버전 3.7.13 (2012-06-06)부터 데이터베이스가 URI 파일 이름을 사용하여 생성 된 경우 메모리 내 데이터베이스에서 공유 캐시를 사용할 수 있습니다.  이전 버전과의 호환성을 위해 공유되지 않은 이름 ": memory :"가 데이터베이스를 여는 데 사용되는 경우 공유 메모리는 메모리 내 데이터베이스에서 항상 비활성화됩니다.  버전 3.7.13 이전에는 사용 된 데이터베이스 이름, 현재 시스템 공유 캐시 설정 또는 쿼리 매개 변수 또는 플래그와 상관없이 메모리 내 데이터베이스에 대한 공유 캐시가 항상 비활성화되었습니다.

 메모리 내 데이터베이스에 공유 캐시를 사용하면 같은 프로세스의 두 개 이상의 데이터베이스 연결이 동일한 메모리 내 데이터베이스에 액세스 할 수 있습니다.  공유 캐시에있는 메모리 내 데이터베이스는 자동으로 삭제되고 해당 데이터베이스에 대한 마지막 연결이 닫히면 메모리가 회수됩니다.

'DB' 카테고리의 다른 글

(SQLite) insert 쿼리 수행 속도 높이기  (3) 2018.01.20
SQLite 참고 링크 모음  (1) 2017.11.23
SQLite 공유 캐시 모드  (0) 2017.08.24
[Oracle] 시간 타입 컬럼 시분초 형태로 결과보기 (nls_date)  (0) 2017.06.28
ORACLE vs DB2  (0) 2017.05.30
(SQLite) SQLite with GPU  (0) 2017.04.30

+ Recent posts