MySQL EXPLAINの読み方をJavaで理解する(1)

はてなブックマークの人気エントリで「SQLを使うなら理解しておきたいアルゴリズム?…」という記事を眺めていて、実際にSQLアルゴリズムを説明するにはどうすればよいか考えてみました。そこで、試しにSQLの実行計画をJavaで再現してみることにしました。
今回は索引アクセスとテーブル結合に関する7種類のSQLについて、MySQLのEXPLAIN結果とそれに対応するJavaのコードを記述しました。EXPLAINの読み方を言葉で説明するのは非常に難しいのですが、プログラムを読める方ならこうした説明が一番分かりやすいのではないでしょうか。

import java.util.ArrayList;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;

public class Study {
	public static void main(String[] args) {
		new Study().test();
	}

	private void test() {
// SQL 01
// CREATE TABLE emp (empno INT, ename VARCHAR(100), deptno INT);
		List<Employee> empTable = new ArrayList<Employee>();

// SQL 02
// INSERT INTO emp (empno, ename, deptno) VALUES (xxx, 'yyy', zzz);
		empTable.add(new Employee(7782, "clark", 10));
		empTable.add(new Employee(7788, "scott", 20));
		empTable.add(new Employee(7839, "king", 70));

// SQL 03
// SELECT ename FROM emp;
// +----+-------------+-------+------+---------------+------+---------+------+------+-------+
// | id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra |
// +----+-------------+-------+------+---------------+------+---------+------+------+-------+
// |  1 | SIMPLE      | emp   | ALL  | NULL          | NULL | NULL    | NULL |    3 |       |
// +----+-------------+-------+------+---------------+------+---------+------+------+-------+
		System.out.println("-- SQL 03");
		for (Employee emp : empTable) {
			System.out.println(emp.getEname());
		}

// SQL 04
// SELECT ename FROM emp WHERE empno = 7788;
// +----+-------------+-------+------+---------------+------+---------+------+------+-------------+
// | id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       |
// +----+-------------+-------+------+---------------+------+---------+------+------+-------------+
// |  1 | SIMPLE      | emp   | ALL  | NULL          | NULL | NULL    | NULL |    3 | Using where |
// +----+-------------+-------+------+---------------+------+---------+------+------+-------------+
		System.out.println("-- SQL 04");
		for (Employee emp : empTable) {
			if (emp.getEmpno() == 7788) {
				System.out.println(emp.getEname());
			}
		}

// SQL 05
// CREATE UNIQUE INDEX emp_empno ON emp (empno);
		SortedMap<Integer, Employee> empEmpnoIdx = new TreeMap<Integer, Employee>();
		for (Employee emp : empTable) {
			empEmpnoIdx.put(emp.getEmpno(), emp);
		}

// SQL 06 (SQL 04と同じ)
// SELECT ename FROM emp WHERE empno = 7788;
// +----+-------------+-------+-------+---------------+-----------+---------+-------+------+-------+
// | id | select_type | table | type  | possible_keys | key       | key_len | ref   | rows | Extra |
// +----+-------------+-------+-------+---------------+-----------+---------+-------+------+-------+
// |  1 | SIMPLE      | emp   | const | emp_empno     | emp_empno | 5       | const |    1 |       |
// +----+-------------+-------+-------+---------------+-----------+---------+-------+------+-------+
		System.out.println("-- SQL 06");
		if (empEmpnoIdx.containsKey(7788)) {
			Employee emp = empEmpnoIdx.get(7788);
			System.out.println(emp.getEname());
		}

// SQL 07
// CREATE TABLE dept (deptno INT, dname VARCHAR(100));
		List<Department> deptTable = new ArrayList<Department>();

// SQL 08
// CREATE UNIQUE INDEX dept_deptno ON dept (deptno);
		SortedMap<Integer, Department> deptDeptnoIdx = new TreeMap<Integer, Department>();

// SQL 09
// INSERT INTO dept (deptno, dname) VALUES (xxx, 'yyy');
		Department deptTmp = new Department(10, "accounting");
		deptTable.add(deptTmp);
		deptDeptnoIdx.put(deptTmp.getDeptno(), deptTmp);
		deptTmp = new Department(20, "research");
		deptTable.add(deptTmp);
		deptDeptnoIdx.put(deptTmp.getDeptno(), deptTmp);
		deptTmp = new Department(30, "sales");
		deptTable.add(deptTmp);
		deptDeptnoIdx.put(deptTmp.getDeptno(), deptTmp);
		deptTmp = new Department(40, "operations");
		deptTable.add(deptTmp);
		deptDeptnoIdx.put(deptTmp.getDeptno(), deptTmp);
		deptTmp = new Department(50, "consulting");
		deptTable.add(deptTmp);
		deptDeptnoIdx.put(deptTmp.getDeptno(), deptTmp);
		deptTmp = new Department(60, "education");
		deptTable.add(deptTmp);
		deptDeptnoIdx.put(deptTmp.getDeptno(), deptTmp);

// SQL 10
// SELECT e.ename, d.dname FROM emp e INNER JOIN dept d ON e.deptno = d.deptno;
// SQL 10'
// SELECT e.ename, d.dname FROM emp e, dept d WHERE e.deptno = d.deptno;
// +----+-------------+-------+------+---------------+-------------+---------+----------------+------+-------------+
// | id | select_type | table | type | possible_keys | key         | key_len | ref            | rows | Extra       |
// +----+-------------+-------+------+---------------+-------------+---------+----------------+------+-------------+
// |  1 | SIMPLE      | e     | ALL  | NULL          | NULL        | NULL    | NULL           |    3 |             |
// |  1 | SIMPLE      | d     | ref  | dept_deptno   | dept_deptno | 5       | study.e.deptno |    1 | Using where |
// +----+-------------+-------+------+---------------+-------------+---------+----------------+------+-------------+
		System.out.println("-- SQL 10");
		for (Employee emp : empTable) {
			if (deptDeptnoIdx.containsKey(emp.getDeptno())) {
				Department dept = deptDeptnoIdx.get(emp.getDeptno());
				System.out.println(emp.getEname() + "," + dept.getDname());
			}
		}

// SQL 11
// SELECT e.ename, d.dname FROM emp e LEFT OUTER JOIN dept d ON e.deptno = d.deptno;
// +----+-------------+-------+------+---------------+-------------+---------+----------------+------+-------+
// | id | select_type | table | type | possible_keys | key         | key_len | ref            | rows | Extra |
// +----+-------------+-------+------+---------------+-------------+---------+----------------+------+-------+
// |  1 | SIMPLE      | e     | ALL  | NULL          | NULL        | NULL    | NULL           |    3 |       |
// |  1 | SIMPLE      | d     | ref  | dept_deptno   | dept_deptno | 5       | study.e.deptno |    1 |       |
// +----+-------------+-------+------+---------------+-------------+---------+----------------+------+-------+
		System.out.println("-- SQL 11");
		for (Employee emp : empTable) {
			if (deptDeptnoIdx.containsKey(emp.getDeptno())) {
				Department dept = deptDeptnoIdx.get(emp.getDeptno());
				System.out.println(emp.getEname() + "," + dept.getDname());
			} else {
				System.out.println(emp.getEname() + "," + "(null)");
			}
		}

// SQL 12
// SELECT e.ename, d.dname FROM emp e INNER JOIN dept d ON e.deptno = d.deptno WHERE e.empno = 7788;
// +----+-------------+-------+-------+---------------+-------------+---------+-------+------+-------+
// | id | select_type | table | type  | possible_keys | key         | key_len | ref   | rows | Extra |
// +----+-------------+-------+-------+---------------+-------------+---------+-------+------+-------+
// |  1 | SIMPLE      | e     | const | emp_empno     | emp_empno   | 5       | const |    1 |       |
// |  1 | SIMPLE      | d     | const | dept_deptno   | dept_deptno | 5       | const |    1 |       |
// +----+-------------+-------+-------+---------------+-------------+---------+-------+------+-------+
		System.out.println("-- SQL 12");
		if (empEmpnoIdx.containsKey(7788)) {
			Employee emp = empEmpnoIdx.get(7788);
			if (deptDeptnoIdx.containsKey(emp.getDeptno())) {
				Department dept = deptDeptnoIdx.get(emp.getDeptno());
				System.out.println(emp.getEname() + "," + dept.getDname());
			}
		}

// SQL 13
// SELECT e.ename, d.dname FROM emp e INNER JOIN dept d ON e.deptno = d.deptno WHERE e.ename = 'scott';
// +----+-------------+-------+------+---------------+-------------+---------+----------------+------+-------------+
// | id | select_type | table | type | possible_keys | key         | key_len | ref            | rows | Extra       |
// +----+-------------+-------+------+---------------+-------------+---------+----------------+------+-------------+
// |  1 | SIMPLE      | e     | ALL  | NULL          | NULL        | NULL    | NULL           |    3 | Using where |
// |  1 | SIMPLE      | d     | ref  | dept_deptno   | dept_deptno | 5       | study.e.deptno |    1 | Using where |
// +----+-------------+-------+------+---------------+-------------+---------+----------------+------+-------------+
		System.out.println("-- SQL 13");
		for (Employee emp : empTable) {
			if (emp.getEname().equals("scott")) {
				if (deptDeptnoIdx.containsKey(emp.getDeptno())) {
					Department dept = deptDeptnoIdx.get(emp.getDeptno());
					System.out.println(emp.getEname() + "," + dept.getDname());
				}
			}
		}
	}
}

class Employee {
	private int empno;
	private String ename;
	private int deptno;

	public Employee(int empno, String ename, int deptno) {
		this.empno = empno;
		this.ename = ename;
		this.deptno = deptno;
	}

	public int getEmpno() {
		return empno;
	}

	public String getEname() {
		return ename;
	}

	public int getDeptno() {
		return deptno;
	}
}

class Department {
	private int deptno;
	private String dname;

	public Department(int deptno, String dname) {
		this.deptno = deptno;
		this.dname = dname;
	}

	public int getDeptno() {
		return deptno;
	}

	public String getDname() {
		return dname;
	}
}
-- SQL 03
clark
scott
king
-- SQL 04
scott
-- SQL 06
scott
-- SQL 10
clark,accounting
scott,research
-- SQL 11
clark,accounting
scott,research
king,(null)
-- SQL 12
scott,research
-- SQL 13
scott,research

いかがでしょう。それほど難しくないですよね。
解説を別エントリとして起こしました。あわせてご覧ください。