set @num_gamers := 10000, @num_countries := 5, @num_games := 10; drop table if exists gamer; drop table if exists game; drop table if exists country; drop table if exists score; drop table if exists semaphore; create table semaphore(i int not null primary key); insert into semaphore(i) values (0); create table gamer( gamer int not null, country int not null, name varchar(20) not null, primary key(gamer) ) engine=InnoDB; create table game( game int not null, name varchar(20) not null, primary key(game) ) engine=InnoDB; create table score( gamer int not null, game int not null, score int not null, primary key(gamer, game), index(game, score), index(score) ) engine=InnoDB; create table country( country int not null, name varchar(20) not null, primary key(country) ) engine=InnoDB; -- 샘플 데이터를 생성하기 위해서 integers 테이블을 사용 drop table if exists integers; create table integers(i int not null primary key); insert into integers(i) values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9); insert into country(country, name) select t.i * 10 + u.i, concat("country", t.i * 10 + u.i) from integers as u cross join integers as t where t.i * 10 + u.i < @num_countries; insert into game(game, name) select t.i * 10 + u.i, concat("game", t.i * 10 + u.i) from integers as u cross join integers as t where t.i * 10 + u.i < @num_games; insert into gamer(gamer, name, country) select th.i * 1000 + h.i * 100 + t.i * 10 + u.i, concat("gamer", th.i * 1000 + h.i * 100 + t.i * 10 + u.i), floor(rand(0) * @num_countries) from integers as u cross join integers as t cross join integers as h cross join integers as th; insert into score(gamer, game, score) select gamer.gamer, game.game, floor(rand(0) * @num_gamers * 10) from gamer cross join game;랭킹 게시판의 페이징 처리를 위한 전형적인 쿼리
-- 게임 1번의 1010번 유저를 찾는다. 결과 : 97101 select score from score where gamer = 1010 and game = 1; -- 1010번 게이머가 몇 번째에 위치하고 있는가? 결과 : 309. -- 동점인 경우는 게이머의 ID를 이용해서 구분한다. select count(*) from score where score.game = 1 and score.score >= 97101; -- 1010번 게이머가 위치하게 될 랭킹 게시판 페이지의 데이터를 찾는다. select gamer.gamer, gamer.name, score.score from gamer inner join score on gamer.gamer = score.gamer where score.game = 1 order by score.score desc limit 300, 20; -- 찾은 페이지의 제일 처음에 나오는 결과값의 랭킹을 찾는다. 결과값 : 284. select count(distinct score.score) from score where score.game = 1 and score.score >= 97101;마지막 Query는 랭킹 게시판에 표시할 랭킹을 알아내기 위해서 사용한다. 그 위의 Query에서는 표시할 row 들을 가져올 수는 있지만, 몇 번째 랭킹인지까지는 가져올 수가 없다. 그래서 위의 Query를 이용해서 페이지의 첫 번째 랭킹정보를 받아온 후에 차례차례로 다음row를 출력하면서 바로 이전의 유저정보의 게임 점수와 점수가 다른 row가 나오게 되면 랭킹을 1씩 증가시켜 주어야 한다.
-- 위에서 설명된 바와 같이 게임 점수와 랭킹을 찾는다. select score, rank_in_game from score_ranked where gamer = 1010 and game = 1; -- 랭킹게시판에서의 유저의 위치를 찾는다. 결과 값 : 309 select count(*) from score_ranked where game = 1 and score >= 97101; select gamer.gamer, gamer.name, u.score, u.rank_in_game from gamer inner join ( ( -- Fetch start (first 9 rows) of leaderboard. select gamer, score, rank_in_game from score_ranked where game = 1 and rank_in_game <= 290 and gamer <= 1010 order by rank_in_game desc, gamer desc limit 9 ) union all ( -- Fetch remaining 11 rows. select gamer, score, rank_in_game from score_ranked where game = 1 and ((gamer > 1010 and rank_in_game = 290) or rank_in_game > 290) order by rank_in_game, gamer limit 11 ) order by rank_in_game, gamer ) as u on gamer.gamer = u.gamer;좀 복잡해 보이기는 하지만, 이전 Query에 비하면 효과가 있다. Query에서 COUNT() 명령을 제거하였고, LIMIT 명령에서 사용하고 있던 Offset 값또한 사용하지 않게 되었다. 이제 위에서 작성된 Query를 수행하게 되면 랭킹을 포함한 게시판에 필요한 row들을 얻을 수가 있다.
-- 위에서 설명된 바와 같이 게임 점수와 랭킹을 찾는다. select score, rank_in_game from score_ranked where gamer = 1010 and game = 1; -- 1010번 게이머가 속한 랭킹 게시판의 페이지 데이터를 찾는다. select gamer.gamer, gamer.name, u.score, u.rank_in_game from gamer inner join score_ranked as u on gamer.gamer = u.gamer where game = 1 and rank_in_game >= 290 and u.gamer >= 1010 order by rank_in_game, gamer limit 20;더 많이 좋아졌다. 이 Query를 이용하면 최소한의 row를 읽어서 우리가 원하는 랭킹 정보를 얻어낼 수가 있게 되었다. 다른 종류의 랭킹 게시판(예를 들면, 나라를 기준으로 혹은 전체 랭킹 정보 등)또한 같은 방법으로 최적화가 가능할 것이다.
이전 글 : 예제로 살펴보는 쓰레드 제어하기 - (2)
다음 글 : MySQL에서의 랭킹 데이터 최적화 - (2)
최신 콘텐츠