On Github laysakura / 20131110-DB-Presentation
2013/11/10 @ 内定者勉強会
硬派にやります
要点: 基本の5操作覚えれば大体書けるようになる
-- TPC-H Query#10 select c_custkey, c_name, sum(l_extendedprice * (1 - l_discount)) as revenue, c_acctbal, n_name, c_address, c_phone, c_comment from customer, orders, lineitem, nation where c_custkey = o_custkey and l_orderkey = o_orderkey and o_orderdate >= date ':1' and o_orderdate < date ':1' + interval '3' month and l_returnflag = 'R' and c_nationkey = n_nationkey group by c_custkey, c_name, c_acctbal, c_phone, n_name, c_address, c_comment order by revenue desc;
関係代数基本の5操作の組み合わせでしかない
select * from Giants where id >= 3 and id <= 5; -- 往年のクリーンナップ・・・
select * from Giants where id >= 3 and id <= 5; -- 往年のクリーンナップ・・・
select name from Giants; -- 名前だけとる
select name from Giants; -- 名前だけとる
select * from Giants order by age; -- 年齢でソート
select * from Giants order by age; -- 年齢でソート
select avg(age) from Giants; -- 平均年齢
select avg(age) from Giants; -- 平均年齢avg(age) 28.88
select unit, avg(age) from Member group by unit; -- ユニットごとの平均年齢id name unit age 1 まゆゆ AKB 23 2 松井 SKE 18 3 前田 AKB 27
select unit, avg(age) from Member group by unit; -- ユニットごとの平均年齢unit age AKB 25 SKE 18
select Member.name, Fan.name, Fan.age from Member, Fan where Fan.oshimen = Member.name; -- 各メンバーは誰に愛されているか
Member
id name unit age 1 まゆゆ AKB 23 2 松井 SKE 18 3 前田 AKB 27Fan
id name oshimen 1 ブヒブヒ1 松井 2 ブヒブヒ2 まゆゆ 3 ブヒブヒ3 松井select Member.name, Fan.name, Fan.age from Member, Fan where Fan.oshimen = Member.name; -- 各メンバーは誰に愛されているか
結果
Member.name Fan.name Fan.age 松井 ブヒブヒ1 25 松井 ブヒブヒ3 22 まゆゆ ブヒブヒ2 38selection, projection, sort, aggregation, join はどこかな?
-- TPC-H Query#10 select c_custkey, c_name, sum(l_extendedprice * (1 - l_discount)) as revenue, c_acctbal, n_name, c_address, c_phone, c_comment from customer, orders, lineitem, nation where c_custkey = o_custkey and l_orderkey = o_orderkey and o_orderdate >= date ':1' and o_orderdate < date ':1' + interval '3' month and l_returnflag = 'R' and c_nationkey = n_nationkey group by c_custkey, c_name, c_acctbal, c_phone, n_name, c_address, c_comment order by revenue desc;
要点: インデックスのデータ構造と使われどころを抑えよう
-- ゲームユーザの情報テーブル作成 create table User ( user_id INTEGER PRIMARY KEY AUTO_INCREMENT, name VARCHAR(64), score INTEGER ); -- ユーザがどんどん追加される insert into User values ('たかし', 300); insert into User values ('みか', 500); ... select count(*) from User; -- => 100万行
やりたい処理: scoreが高いユーザを列挙する
-- インデックスなし select id from User where score > 10000; -- => 10秒 -- インデックスあり create index score_idx on User(score); select id from User where score > 10000; -- => 1秒
インデックスを貼る = ソート済みの 'リスト' を作る
# インデックスなし: # scoreが10000より大きい要素を探す => 全探索 O(n) scores = [300, 500, 200, 15000, 15, ..., 20000, 10, ...] # インデックスあり = ソート済みのリスト: # scoreが10000より大きい要素を探す => 二分探索 O(log n) scores = [10, 15, 200, 300, 500, ..., 15000, 20000, ...]
「カラムXにインデックス貼ると,カラムXのソート済みリストができるんだ!!」
ディスクに置く & 「ソート済み」なデータ構造 => B+Tree
(ディスク ≒ ハードディスクの話)
なぜリスト構造はディスクに置かれるとそのまま使えないのか?
リストの各要素は飛び飛びのメモリ番地に配置されている
ディスクはランダムアクセスめっちゃ遅い(HDDの物理的構造の話)
少なくとも古典的には標題が成り立つ
(Jeffrey D. Ullman 氏のサイト より引用)
(Jeffrey D. Ullman 氏のサイト より引用)
(Jeffrey D. Ullman 氏のサイト より引用)
(Jeffrey D. Ullman 氏のサイト より引用)
select * from User where score=29;
select * from User where score=29;
(1) ルートノードからスタートし,13と29を比較 => 13より大きいので右の子ノードへ
select * from User where score=29;
(2) 23より大きく31より小さいので,左から2番目の子ノードへ
select * from User where score=29;
(3) 29が見つかり,それとペアの主キーを元にレコードを取ってこれる
select * from User where score >= 29 and score <= 37;
(1) 29が見つかるとこまでは先程と同様
select * from User where score >= 29 and score <= 37;
(2) 29のあるノードにはもうインデックス値がない => 隣の末端ノードのリンクを辿る
select * from User where score >= 29 and score <= 37;
(3) 37が見つかったところまででストップし,29,31,37に対応する主キーからレコードを取る
「インデックスすごい!もう全部のカラムに貼っちゃう!!」
インデックスのデメリット
インデックスを貼るカラムを判断できる => 一人前
要点: 並列処理の '怖さ' を知ろう
-- ユーザ登録リクエストをDBに反映 insert into User (name, age) values ('たかし', 29); -- ユーザ数をサービス統計に反映 update Service set num_users = num_users + 1;
-- ユーザ登録リクエストをDBに反映 insert into User (name, age) values ('たかし', 29); -- ここで外から count(*) from User と Service.num_users が参照されると? -- => count(*) from User > Service.num_users -- ユーザ数をサービス統計に反映 update Service set num_users = num_users + 1;
UserテーブルとServiceテーブルの整合性が崩れている
BEGIN; -- トランザクションの開始 -- ユーザ登録リクエストをDBに反映 insert into User (name, age) values ('たかし', 29); -- トランザクション中は,外から「この状態」は見られない! -- (たかしユーザが追加されたことが観測できるのはCOMMIT以後) -- ユーザ数をサービス統計に反映 update Service set num_users = num_users + 1; COMMIT; -- トランザクションの終了
client1> select * from User; client2> insert into User (name, age) values ('たかし', 29); client1> select * from User; -- たかしくんは追加されてる client2> BEGIN; client2> insert into User (name, age) values ('みか', 27); client1> select * from User; -- みかちゃんがいない!! client2> COMMIT; client1> select * from User; -- みかちゃんが見えるように
(これ書いてる時はハロウィンでした)
client1> select * from User; client2> insert into User (name, age) values ('たかし', 29); client1> select * from User; -- たかしくんは追加されてる client2> BEGIN; client2> insert into User (name, age) values ('みか', 27); client1> select * from User; -- みかちゃんがいない!! -- ここまでデモ1と同じ client2> ROLLBACK; client1> select * from User; -- みかちゃんはなかったことに・・・
insertとかupdateとかするとき,「暗黙のトランザクション」が発行されている
トランザクション貼るのは重たい処理
暗黙のトランザクションよりも自前トランザクションを使ったほうが速いことも
for (my $i = 0; $i < 200; ++$i) { $dbh->do('insert into T1 values(777)'); # トランザクションが200回 => 遅い } ### $dbh->do('BEGIN'); for (my $i = 0; $i < 200; ++$i) { $dbh->do('insert into T2 values(777)'); # トランザクションは1回 => 速い } $dbh->do('COMMIT');
RDBMSのトランザクションはACID性を満たすのが普通
SQL(楽さ)
インデックス(速さ)
トランザクション(安心さ)
今後の勉強の指針にしてください(優先度順)
↓
個人的には嫌いです(パフォーマンス出んやろ)
(俺も勉強しなきゃ・・・)
これまた優先度順番
↓
インフラ屋になりたいなら必須の内容
ありがとうございました!