链接数据库
打开数据库
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的配置,以求最大优化的使用数据,得到更高的效率。下面我们开始分析这个过程。
- 初始化sqlite。
- 设置统计信息,使得可以通过浏览器chrome://histograms/获得当前的统计数据
- 使用sqlite3_open打开数据库。
- 设置SQLITE_DBCONFIG_LOOKASIDE,这是为了提升小量存储申请的效率优化
- 设置locking_mode和journal_mode,page-size, cache_size, secure_delete。
- 使用SQLITE_FCNTL_CHUNK_SIZE设置文件系统chunk_size.
- 设置 mmap_size.
- 注册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);
}
这是数据库语句的执行函数,首先判断语句是否有效,如果有效,则判断数据库是否连接,如果连接则执行该条语句。如果设置了执行时间统计,那么要调用接口记录单条语句执行的时间。