sql

链接数据库

打开数据库

chromium数据库使用的是sqlite进行存储数据的,必然在使用的时候需要一个包装,这个项目是sql,其位于chromium的一级目录中,内部项目并不十分复杂,慢慢的进行分析。

首先我们要分析的是链接数据库。在一开始,我们要了解,数据库的访问是在数据库线程中的,而不是在UI线程的,这取决于chromium的多线程机制。

Connection类是数据库sql的链接类,用来创建数据库sqlite的链接,初始化数据库,设置数据库等相关操作。

0:022> kL
 # Child-SP          RetAddr           Call Site
00 00000000`0662dc18 000007fe`ee26a8fe chrome_7feed330000!sql::Connection::OpenInternal
01 00000000`0662dc20 000007fe`ef53ad74 chrome_7feed330000!sql::Connection::Open+0x10e
02 00000000`0662dcc0 000007fe`ef53c12f chrome_7feed330000!WebDatabase::Init+0xa0
03 00000000`0662de60 000007fe`ef53c05f chrome_7feed330000!WebDatabaseBackend::LoadDatabaseIfNecessary+0xb3
04 00000000`0662dfa0 000007fe`ed3d2ead chrome_7feed330000!WebDatabaseBackend::InitDatabase+0x13
05 (Inline Function) --------`-------- chrome_7feed330000!base::Callback<void __cdecl(void)>::Run+0x8
06 00000000`0662dfd0 000007fe`ed36eb95 chrome_7feed330000!base::debug::TaskAnnotator::RunTask+0x13d
07 00000000`0662e0b0 000007fe`ed36f6d4 chrome_7feed330000!base::MessageLoop::RunTask+0x3f5
08 (Inline Function) --------`-------- chrome_7feed330000!base::MessageLoop::DeferOrRunPendingTask+0x147
09 00000000`0662f190 000007fe`ed3d0e86 chrome_7feed330000!base::MessageLoop::DoWork+0x484
0a 00000000`0662f360 000007fe`ed3bdd83 chrome_7feed330000!base::MessagePumpDefault::Run+0x1f6
0b (Inline Function) --------`-------- chrome_7feed330000!base::MessageLoop::RunHandler+0x15
0c 00000000`0662f5c0 000007fe`ed394381 chrome_7feed330000!base::RunLoop::Run+0x83
0d (Inline Function) --------`-------- chrome_7feed330000!base::MessageLoop::Run+0x35
0e 00000000`0662f610 000007fe`ee3a8076 chrome_7feed330000!base::Thread::Run+0x41
0f 00000000`0662f670 000007fe`ee3a8d3e chrome_7feed330000!content::BrowserThreadImpl::DBThreadRun+0x36
10 00000000`0662f7c0 000007fe`ed3946d8 chrome_7feed330000!content::BrowserThreadImpl::Run+0xca
11 00000000`0662f7f0 000007fe`ed3a7e9d chrome_7feed330000!base::Thread::ThreadMain+0x338
12 00000000`0662f860 00000000`77a4652d chrome_7feed330000!base::`anonymous namespace'::ThreadFunc+0x15d
13 00000000`0662f8d0 00000000`77ccc521 kernel32!BaseThreadInitThunk+0xd
14 00000000`0662f900 00000000`00000000 ntdll!RtlUserThreadStart+0x1d

上面的一个堆栈情况,是打开webdata数据库的函数调用情况,这个线程专门用来处理数据库数据。

bool Connection::OpenInternal(const std::string& file_name,
                              Connection::Retry retry_flag) {
  AssertIOAllowed();

  if (db_) {
    DLOG(FATAL) << "sql::Connection is already open.";
    return false;
  }

  // Make sure sqlite3_initialize() is called before anything else.
  InitializeSqlite();

  // Setup the stats histograms immediately rather than allocating lazily.
  // Connections which won't exercise all of these probably shouldn't exist.
  if (!histogram_tag_.empty()) {
    stats_histogram_ =
        base::LinearHistogram::FactoryGet(
            "Sqlite.Stats." + histogram_tag_,
            1, EVENT_MAX_VALUE, EVENT_MAX_VALUE + 1,
            base::HistogramBase::kUmaTargetedHistogramFlag);

    // The timer setup matches UMA_HISTOGRAM_MEDIUM_TIMES().  3 minutes is an
    // unreasonable time for any single operation, so there is not much value to
    // knowing if it was 3 minutes or 5 minutes.  In reality at that point
    // things are entirely busted.
    commit_time_histogram_ =
        GetMediumTimeHistogram("Sqlite.CommitTime." + histogram_tag_);

    autocommit_time_histogram_ =
        GetMediumTimeHistogram("Sqlite.AutoCommitTime." + histogram_tag_);

    update_time_histogram_ =
        GetMediumTimeHistogram("Sqlite.UpdateTime." + histogram_tag_);

    query_time_histogram_ =
        GetMediumTimeHistogram("Sqlite.QueryTime." + histogram_tag_);
  }

  // If |poisoned_| is set, it means an error handler called
  // RazeAndClose().  Until regular Close() is called, the caller
  // should be treating the database as open, but is_open() currently
  // only considers the sqlite3 handle's state.
  // TODO(shess): Revise is_open() to consider poisoned_, and review
  // to see if any non-testing code even depends on it.
  DLOG_IF(FATAL, poisoned_) << "sql::Connection is already open.";
  poisoned_ = false;

  int err = sqlite3_open(file_name.c_str(), &db_);
  if (err != SQLITE_OK) {
    // Extended error codes cannot be enabled until a handle is
    // available, fetch manually.
    err = sqlite3_extended_errcode(db_);

    // Histogram failures specific to initial open for debugging
    // purposes.
    UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.OpenFailure", err);

    OnSqliteError(err, NULL, "-- sqlite3_open()");
    bool was_poisoned = poisoned_;
    Close();

    if (was_poisoned && retry_flag == RETRY_ON_POISON)
      return OpenInternal(file_name, NO_RETRY);
    return false;
  }

  // TODO(shess): OS_WIN support?
#if defined(OS_POSIX)
  if (restrict_to_user_) {
    DCHECK_NE(file_name, std::string(":memory"));
    base::FilePath file_path(file_name);
    int mode = 0;
    // TODO(shess): Arguably, failure to retrieve and change
    // permissions should be fatal if the file exists.
    if (base::GetPosixFilePermissions(file_path, &mode)) {
      mode &= base::FILE_PERMISSION_USER_MASK;
      base::SetPosixFilePermissions(file_path, mode);

      // SQLite sets the permissions on these files from the main
      // database on create.  Set them here in case they already exist
      // at this point.  Failure to set these permissions should not
      // be fatal unless the file doesn't exist.
      base::FilePath journal_path(file_name + FILE_PATH_LITERAL("-journal"));
      base::FilePath wal_path(file_name + FILE_PATH_LITERAL("-wal"));
      base::SetPosixFilePermissions(journal_path, mode);
      base::SetPosixFilePermissions(wal_path, mode);
    }
  }
#endif  // defined(OS_POSIX)

  // SQLite uses a lookaside buffer to improve performance of small mallocs.
  // Chromium already depends on small mallocs being efficient, so we disable
  // this to avoid the extra memory overhead.
  // This must be called immediatly after opening the database before any SQL
  // statements are run.
  sqlite3_db_config(db_, SQLITE_DBCONFIG_LOOKASIDE, NULL, 0, 0);

  // Enable extended result codes to provide more color on I/O errors.
  // Not having extended result codes is not a fatal problem, as
  // Chromium code does not attempt to handle I/O errors anyhow.  The
  // current implementation always returns SQLITE_OK, the DCHECK is to
  // quickly notify someone if SQLite changes.
  err = sqlite3_extended_result_codes(db_, 1);
  DCHECK_EQ(err, SQLITE_OK) << "Could not enable extended result codes";

  // sqlite3_open() does not actually read the database file (unless a
  // hot journal is found).  Successfully executing this pragma on an
  // existing database requires a valid header on page 1.
  // TODO(shess): For now, just probing to see what the lay of the
  // land is.  If it's mostly SQLITE_NOTADB, then the database should
  // be razed.
  err = ExecuteAndReturnErrorCode("PRAGMA auto_vacuum");
  if (err != SQLITE_OK)
    UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.OpenProbeFailure", err);

#if defined(OS_IOS) && defined(USE_SYSTEM_SQLITE)
  // The version of SQLite shipped with iOS doesn't enable ICU, which includes
  // REGEXP support. Add it in dynamically.
  err = sqlite3IcuInit(db_);
  DCHECK_EQ(err, SQLITE_OK) << "Could not enable ICU support";
#endif  // OS_IOS && USE_SYSTEM_SQLITE

  // If indicated, lock up the database before doing anything else, so
  // that the following code doesn't have to deal with locking.
  // TODO(shess): This code is brittle.  Find the cases where code
  // doesn't request |exclusive_locking_| and audit that it does the
  // right thing with SQLITE_BUSY, and that it doesn't make
  // assumptions about who might change things in the database.
  // http://crbug.com/56559
  if (exclusive_locking_) {
    // TODO(shess): This should probably be a failure.  Code which
    // requests exclusive locking but doesn't get it is almost certain
    // to be ill-tested.
    ignore_result(Execute("PRAGMA locking_mode=EXCLUSIVE"));
  }

  // http://www.sqlite.org/pragma.html#pragma_journal_mode
  // DELETE (default) - delete -journal file to commit.
  // TRUNCATE - truncate -journal file to commit.
  // PERSIST - zero out header of -journal file to commit.
  // TRUNCATE should be faster than DELETE because it won't need directory
  // changes for each transaction.  PERSIST may break the spirit of using
  // secure_delete.
  ignore_result(Execute("PRAGMA journal_mode = TRUNCATE"));

  const base::TimeDelta kBusyTimeout =
    base::TimeDelta::FromSeconds(kBusyTimeoutSeconds);

  if (page_size_ != 0) {
    // Enforce SQLite restrictions on |page_size_|.
    DCHECK(!(page_size_ & (page_size_ - 1)))
        << " page_size_ " << page_size_ << " is not a power of two.";
    const int kSqliteMaxPageSize = 32768;  // from sqliteLimit.h
    DCHECK_LE(page_size_, kSqliteMaxPageSize);
    const std::string sql =
        base::StringPrintf("PRAGMA page_size=%d", page_size_);
    ignore_result(ExecuteWithTimeout(sql.c_str(), kBusyTimeout));
  }

  if (cache_size_ != 0) {
    const std::string sql =
        base::StringPrintf("PRAGMA cache_size=%d", cache_size_);
    ignore_result(ExecuteWithTimeout(sql.c_str(), kBusyTimeout));
  }

  if (!ExecuteWithTimeout("PRAGMA secure_delete=ON", kBusyTimeout)) {
    bool was_poisoned = poisoned_;
    Close();
    if (was_poisoned && retry_flag == RETRY_ON_POISON)
      return OpenInternal(file_name, NO_RETRY);
    return false;
  }

  // Set a reasonable chunk size for larger files.  This reduces churn from
  // remapping memory on size changes.  It also reduces filesystem
  // fragmentation.
  // TODO(shess): It may make sense to have this be hinted by the client.
  // Database sizes seem to be bimodal, some clients have consistently small
  // databases (<20k) while other clients have a broad distribution of sizes
  // (hundreds of kilobytes to many megabytes).
  sqlite3_file* file = NULL;
  sqlite3_int64 db_size = 0;
  int rc = GetSqlite3FileAndSize(db_, &file, &db_size);
  if (rc == SQLITE_OK && db_size > 16 * 1024) {
    int chunk_size = 4 * 1024;
    if (db_size > 128 * 1024)
      chunk_size = 32 * 1024;
    sqlite3_file_control(db_, NULL, SQLITE_FCNTL_CHUNK_SIZE, &chunk_size);
  }

  // Enable memory-mapped access.  The explicit-disable case is because SQLite
  // can be built to default-enable mmap.  GetAppropriateMmapSize() calculates a
  // safe range to memory-map based on past regular I/O.  This value will be
  // capped by SQLITE_MAX_MMAP_SIZE, which could be different between 32-bit and
  // 64-bit platforms.
  size_t mmap_size = mmap_disabled_ ? 0 : GetAppropriateMmapSize();
  std::string mmap_sql =
      base::StringPrintf("PRAGMA mmap_size = %" PRIuS, mmap_size);
  ignore_result(Execute(mmap_sql.c_str()));

  // Determine if memory-mapping has actually been enabled.  The Execute() above
  // can succeed without changing the amount mapped.
  mmap_enabled_ = false;
  {
    Statement s(GetUniqueStatement("PRAGMA mmap_size"));
    if (s.Step() && s.ColumnInt64(0) > 0)
      mmap_enabled_ = true;
  }

  DCHECK(!memory_dump_provider_);
  memory_dump_provider_.reset(
      new ConnectionMemoryDumpProvider(db_, histogram_tag_));
  base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
      memory_dump_provider_.get(), "sql::Connection", nullptr);

  return true;
}

这块代码是整个数据库链接类中最值得分析的一块代码,这里面是各种sqlite的配置,以求最大优化的使用数据,得到更高的效率。下面我们开始分析这个过程。

  1. 初始化sqlite。
  2. 设置统计信息,使得可以通过浏览器chrome://histograms/获得当前的统计数据
  3. 使用sqlite3_open打开数据库。
  4. 设置SQLITE_DBCONFIG_LOOKASIDE,这是为了提升小量存储申请的效率优化
  5. 设置locking_mode和journal_mode,page-size, cache_size, secure_delete。
  6. 使用SQLITE_FCNTL_CHUNK_SIZE设置文件系统chunk_size.
  7. 设置 mmap_size.
  8. 注册memory_dump_provider_

预载数据库

预载数据库的原理是获得sqlite3的文件系统接口,调用系统接口读取一定长度的文件。

void Connection::Preload() {
  AssertIOAllowed();

  if (!db_) {
    DLOG_IF(FATAL, !poisoned_) << "Cannot preload null db";
    return;
  }

  // Use local settings if provided, otherwise use documented defaults.  The
  // actual results could be fetching via PRAGMA calls.
  const int page_size = page_size_ ? page_size_ : 1024;
  sqlite3_int64 preload_size = page_size * (cache_size_ ? cache_size_ : 2000);
  if (preload_size < 1)
    return;

  sqlite3_file* file = NULL;
  sqlite3_int64 file_size = 0;
  int rc = GetSqlite3FileAndSize(db_, &file, &file_size);
  if (rc != SQLITE_OK)
    return;

  // Don't preload more than the file contains.
  if (preload_size > file_size)
    preload_size = file_size;

  scoped_ptr<char[]> buf(new char[page_size]);
  for (sqlite3_int64 pos = 0; pos < preload_size; pos += page_size) {
    rc = file->pMethods->xRead(file, buf.get(), page_size, pos);

    // TODO(shess): Consider calling OnSqliteError().
    if (rc != SQLITE_OK)
      return;
  }
}

数据操作

数据库操作由Statement类具体承担,包含执行数据库语句,绑定数据,检查是否执行成功等操作。

int Statement::StepInternal(bool timer_flag) {
  ref_->AssertIOAllowed();
  if (!CheckValid())
    return SQLITE_ERROR;

  const bool was_stepped = stepped_;
  stepped_ = true;
  int ret = SQLITE_ERROR;
  if (!ref_->connection()) {
    ret = sqlite3_step(ref_->stmt());
  } else {
    if (!timer_flag) {
      ret = sqlite3_step(ref_->stmt());
    } else {
      const base::TimeTicks before = ref_->connection()->Now();
      ret = sqlite3_step(ref_->stmt());
      const base::TimeTicks after = ref_->connection()->Now();
      const bool read_only = !!sqlite3_stmt_readonly(ref_->stmt());
      ref_->connection()->RecordTimeAndChanges(after - before, read_only);
    }

    if (!was_stepped)
      ref_->connection()->RecordOneEvent(Connection::EVENT_STATEMENT_RUN);

    if (ret == SQLITE_ROW)
      ref_->connection()->RecordOneEvent(Connection::EVENT_STATEMENT_ROWS);
  }
  return CheckError(ret);
}

这是数据库语句的执行函数,首先判断语句是否有效,如果有效,则判断数据库是否连接,如果连接则执行该条语句。如果设置了执行时间统计,那么要调用接口记录单条语句执行的时间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值