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操作の組み合わせでしかない
名前 | 意味 | SQL |
---|---|---|
selection | 行絞り込み | WHERE |
projection | 列絞り込み | (カラム名指定) |
sort | ソート | ORDER BY |
aggregation | 集約 | GROUP BY, count(*), avg(*), ... |
join | 結合 | JOIN, WHERE条件 |
id | name | age |
---|---|---|
1 | 仁志 | 25 |
2 | 清水 | 18 |
3 | 高橋 | 31 |
4 | 松井 | 33 |
5 | 清原 | 22 |
6 | 江藤 | 21 |
7 | 二岡 | 19 |
8 | 村田 | 63 |
9 | 上原 | 28 |
select * from Giants where id >= 3 and id <= 5; -- 往年のクリーンナップ・・・
id | name | age |
---|---|---|
3 | 高橋 | 31 |
4 | 松井 | 33 |
5 | 清原 | 22 |
select * from Giants where id >= 3 and id <= 5; -- 往年のクリーンナップ・・・
id | name | age |
---|---|---|
1 | 仁志 | 25 |
2 | 清水 | 18 |
3 | 高橋 | 31 |
4 | 松井 | 33 |
5 | 清原 | 22 |
6 | 江藤 | 21 |
7 | 二岡 | 19 |
8 | 村田 | 63 |
9 | 上原 | 28 |
select name from Giants; -- 名前だけとる
name |
---|
仁志 |
清水 |
高橋 |
松井 |
清原 |
江藤 |
二岡 |
村田 |
上原 |
select name from Giants; -- 名前だけとる
id | name | age |
---|---|---|
1 | 仁志 | 25 |
2 | 清水 | 18 |
3 | 高橋 | 31 |
4 | 松井 | 33 |
5 | 清原 | 22 |
6 | 江藤 | 21 |
7 | 二岡 | 19 |
8 | 村田 | 63 |
9 | 上原 | 28 |
select * from Giants order by age; -- 年齢でソート
id | name | age |
---|---|---|
2 | 清水 | 18 |
7 | 二岡 | 19 |
6 | 江藤 | 21 |
5 | 清原 | 22 |
1 | 仁志 | 25 |
9 | 上原 | 28 |
3 | 高橋 | 31 |
4 | 松井 | 33 |
8 | 村田 | 63 |
select * from Giants order by age; -- 年齢でソート
id | name | age |
---|---|---|
1 | 仁志 | 25 |
2 | 清水 | 18 |
3 | 高橋 | 31 |
4 | 松井 | 33 |
5 | 清原 | 22 |
6 | 江藤 | 21 |
7 | 二岡 | 19 |
8 | 村田 | 63 |
9 | 上原 | 28 |
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 | 27 |
Fan
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 | 38 |
selection, 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, ...]
O(log n)
とかの話がわからない場合「カラムXにインデックス貼ると,カラムXのソート済みリストができるんだ!!」
ディスクに置く & 「ソート済み」なデータ構造 => B+Tree
(ディスク ≒ ハードディスクの話)
なぜリスト構造はディスクに置かれるとそのまま使えないのか?
リストの各要素は飛び飛びのメモリ番地に配置されている
ディスクはランダムアクセスめっちゃ遅い(HDDの物理的構造の話)
少なくとも古典的には標題が成り立つ
(Jeffrey D. Ullman 氏のサイト より引用)
(Jeffrey D. Ullman 氏のサイト より引用)
2,3,5
がインデックスの値,矢印が主キーを指している(Jeffrey D. Ullman 氏のサイト より引用)
(Jeffrey D. Ullman 氏のサイト より引用)
select * from User where score=29;
User.score
インデックスのB+Treescore=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
に対応する主キーからレコードを取る
order by key
, group by key
, join A.key1 on B.key2
のそれぞれで,keyにインデックスがあるときとないときの速度差を見てみる「インデックスすごい!もう全部のカラムに貼っちゃう!!」
インデックスのデメリット
insert
は遅くなるO(log n)
O(1)
インデックスを貼るカラムを判断できる => 一人前
要点: 並列処理の '怖さ' を知ろう
-- ユーザ登録リクエストを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; -- みかちゃんが見えるように
(これ書いてる時はハロウィンでした)
BEGIN
)の後にやれることは2通りCOMMIT
: トランザクション中の処理をDBに反映ROLLBACK
: トランザクション中の処理をなかったことに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
とかするとき,「暗黙のトランザクション」が発行されている
insert
で100byteのレコードを挿入する際,60byte目が挿入された中途半端な状態は他のクライアントから観測できないトランザクション貼るのは重たい処理
暗黙のトランザクションよりも自前トランザクションを使ったほうが速いことも
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(楽さ)
インデックス(速さ)
トランザクション(安心さ)
$dbh->do()
3回で得ていた結果が1回で得られるようにEXPLAIN
コマンド
User
テーブルと Service.num_users
カラムに同時に反映させる(整合性のためのトランザクション)insert
の前後で一つの大きなトランザクション(速さのためのトランザクション)今後の勉強の指針にしてください(優先度順)
↓
個人的には嫌いです(パフォーマンス出んやろ)
(俺も勉強しなきゃ・・・)
これまた優先度順番
↓
$dbh
, $sth
とお友達になろうインフラ屋になりたいなら必須の内容
ありがとうございました!