質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
SQL

SQL(Structured Query Language)は、リレーショナルデータベース管理システム (RDBMS)のデータベース言語です。大きく分けて、データ定義言語(DDL)、データ操作言語(DML)、データ制御言語(DCL)の3つで構成されており、プログラム上でSQL文を生成して、RDBMSに命令を出し、RDBに必要なデータを格納できます。また、格納したデータを引き出すことも可能です。

PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

CodeIgniter

CodeIgniterは、PHP向けオープンソースのWebアプリケーションフレームワークです。CodeIgniterは覚える構文が少なく、自由度も高いため、PHPを理解していれば構築が簡単です。

Q&A

解決済

7回答

3414閲覧

SQLの高速化について。

awazon78

総合スコア32

SQL

SQL(Structured Query Language)は、リレーショナルデータベース管理システム (RDBMS)のデータベース言語です。大きく分けて、データ定義言語(DDL)、データ操作言語(DML)、データ制御言語(DCL)の3つで構成されており、プログラム上でSQL文を生成して、RDBMSに命令を出し、RDBに必要なデータを格納できます。また、格納したデータを引き出すことも可能です。

PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

CodeIgniter

CodeIgniterは、PHP向けオープンソースのWebアプリケーションフレームワークです。CodeIgniterは覚える構文が少なく、自由度も高いため、PHPを理解していれば構築が簡単です。

0グッド

0クリップ

投稿2019/05/21 03:16

編集2019/05/21 04:32

SQLの高速化について質問です。
とあるselect文でメモリが足りません。。

実現したい事は

最終ポイント獲得日が180日以前だったユーザーを抽出です。
最終的な形としては

Array ( [0] => Array ( [id] => 2 [user_id] => 2 [point] => 387 [max_date] => 1535727600 ) [1] => Array ( [id] => 5 [user_id] => 4 [point] => 1261 [max_date] => 1535727600 ) )

このような感じで、配列で取り出せれば理想です。

現在の環境

テーブルは以下の様な感じです。
イメージ説明

CREATE TABLE `test_point_history` ( `id` int(11) NOT NULL, `user_id` int(11) NOT NULL, `point` int(11) NOT NULL, `date` int(11) NOT NULL, `expire_date` int(11) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; INSERT INTO `test_point_history` (`id`, `user_id`, `point`, `date`, `expire_date`) VALUES (1, 1, 1320, 1519830000, NULL), (2, 2, 387, 1522508400, NULL), (3, 2, 1716, 1525100400, NULL), (4, 1, 1717, 1527778800, NULL), (5, 4, 1261, 1530370800, NULL), (6, 1, 1304, 1533049200, NULL), (7, 2, 1031, 1535727600, NULL), (8, 3, 420, 1538319600, NULL), (9, 4, 168, 1535727600, NULL), (10, 1, 1859, 1540998000, NULL), (11, 1, 1637, 1543590000, NULL), (12, 1, 1125, 1551366000, NULL), (13, 3, 219, 1551366000, NULL);

このテーブルには獲得したポイントを履歴に残すテーブルです。
現在やりたい事は実現できているのですが、実データーは1000万件を超えるので、メモリーエラーになってしまいます。
メモリーの上限値を変更する前にSQLを見直ししたいと思います。

現在実行しているSQL

SELECT `id`, `user_id`, `point`, MAX(date) as max_date FROM `test_point_history` WHERE `expire_date` IS NULL GROUP BY `user_id` HAVING `max_date` <= (UNIX_TIMESTAMP() - 15552000)

以上になります。ご教授宜しくお願いします!

2019/05/21 13:30 追記

配列の時点でメモリーエラーでは?とご意見いただきましたので、SQL文を発行している箇所を追記します。

php

1$this->db = $this->load->database('db_slave', true); 2 3 $this->db->select('user_id,MAX(date) as max_date')->from('point_history_copy'); 4 $this->db->where("expire_date <=", 0); 5 $this->db->group_by('user_id'); 6 $this->db->having('max_date <= (' . $this->time . ' - 15552000)'); 7 $array = $this->db->get()->result_array(); 8 9 return $array; 10//↑returnされる前にメモリーが足りなくてエラーが出ます。

そして現在
php.iniのmemory_limmitは256MBでした。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yambejp

2019/05/21 03:22

日付をintで持つのはマストなのでしょうか?
hihijiji

2019/05/21 03:27

どこでどのようにメモリー不足になってますか?
awazon78

2019/05/21 03:28

dateの方が速度が速いのですか?
tanat

2019/05/21 03:40

エラーメッセージを追記してください
ttyp03

2019/05/21 04:19

恐らくレコード数が大きすぎて配列化するときにメモリーが足りないのでしょう。SQLよりもそれを呼び出しているコードを提示したほうがよいと思います。タグにその言語も追加してください。
awazon78

2019/05/21 04:27

エラーメッセージは allowed memory size of 256MB~~ですね。
awazon78

2019/05/21 04:33

php部分追加しました。フレームワークはcodeigniterです
namda

2019/05/21 07:24

DBの種類をタグに追加してください。MySQLなのかOracleDBなのか、PostgreSQLかSQL_ServerなのかSQLiteなのかAccessなのか。
guest

回答7

0

実データが1,000万件でも質問のSQLでメモリーが不足するように思えません。
コンソールでそのSQLを実行してエラーになるでしょうか?
どちらかと言うと、その結果を配列に変換する際に件数が多くて発生しているように思います。

件数に制限を持たせるか、分割して処理するようにすれば良いのではないでしょうか。

投稿2019/05/21 03:25

編集2019/05/21 03:28
sazi

総合スコア25173

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

awazon78

2019/05/21 03:30

配列の時点で。。確かにそうかもしれないです!
sazi

2019/05/21 05:19 編集

SQLの高速化というよりエラーを解決したいのですよね?
sazi

2019/05/22 01:31

質問に追記された内容を見ると、配列に格納しようとしてのメモリ不足の様ですね。
guest

0

user_idでgorup byするのにidやpointは拾えません
mysqlの場合エラーにならないだけで、いつも同じ値が正しく得られるわけではありません。

とりあえずuser_idとdateにまたがった複合インデックスをつけるところから

triggerのsample

SQL

1CREATE TABLE `test_point_history` ( 2 `id` int(11) NOT NULL, 3 `user_id` int(11) NOT NULL, 4 `point` int(11) NOT NULL, 5 `date` int(11) NOT NULL, 6 `expire_date` int(11) DEFAULT NULL 7) ENGINE=InnoDB DEFAULT CHARSET=utf8; 8 9create table `last_point_get`( 10`user_id` int primary key, 11`date` int 12); 13 14drop trigger if exists trg_aft_insert; 15delimiter // 16create trigger trg_aft_insert_test_point_history after insert on test_point_history 17for each row begin 18insert last_point_get(`user_id`,`date`) values 19(new.user_id,new.date) on duplicate key update `date`=values(`date`); 20end 21// 22delimiter ;
  • データ投入

SQL

1INSERT INTO `test_point_history` (`id`, `user_id`, `point`, `date`, `expire_date`) VALUES 2(1, 1, 1320, 1519830000, NULL), 3(2, 2, 387, 1522508400, NULL), 4(3, 2, 1716, 1525100400, NULL), 5(4, 1, 1717, 1527778800, NULL), 6(5, 4, 1261, 1530370800, NULL), 7(6, 1, 1304, 1533049200, NULL), 8(7, 2, 1031, 1535727600, NULL), 9(8, 3, 420, 1538319600, NULL), 10(9, 4, 168, 1535727600, NULL), 11(10, 1, 1859, 1540998000, NULL), 12(11, 1, 1637, 1543590000, NULL), 13(12, 1, 1125, 1551366000, NULL), 14(13, 3, 219, 1551366000, NULL);
  • 結果

SQL

1SELECT * FROM last_point_get;
user_iddate
11551366000
21535727600
31551366000
41535727600

投稿2019/05/21 03:25

編集2019/05/21 03:46
yambejp

総合スコア114769

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

awazon78

2019/05/21 03:29

この段階では正直pointとidは必要ないのでselect文に入れる必要はありません。言葉不足ですいません。user_idだけ取得できればOKです。
yambejp

2019/05/21 03:36

運用方法を変えるという選択肢があれば相当楽になりますけどありでしょうか? 具体的には、最終ポイント獲得テーブルをつくっておき、 ユーザーがポイントを獲得するたびにtriggerで最新日に更新をしていくことです
awazon78

2019/05/21 04:34

稼働してるアプリになるので、なかなか運用方法までは。。ちょっと難しそうです
yambejp

2019/05/21 04:51

現行のテーブルには影響がない処理なのですでにアプリが可動していても 問題ないはずですが? この手の抜本的な対策をしない限り都度都度maxを拾っていると 集計に時間がかかるのは諦めるしかないですね そもそも私が最初に回答した 「user_idとdateにまたがった複合インデックス」 が効いていればそこそこのパフォーマンスだと思うのですが、 なにかおかしいのかもしれないのでexplainで確認して見たほうがよいかもしれません。
guest

0

CodeIgniter って、たしかログでメモリ結構食ってしまってたはず。
バージョンで違うかもしれないけど、確認してみては?
CodeIgniterが食っていたメモリ

投稿2019/05/22 01:13

退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

awazon78

2019/05/22 04:53

なるほど、、参考になりました!ありがとうございます!結構メモリ食うみたいですね
guest

0

result_array()を実行すると結果を全てPHP配列に変換してしまうので、
結果レコード数が多いSQLに対してresult_array()を呼ぶとメモリを大量に使います。

代わりにunbuffered_row()メソッドを使うやり方があります。
こちらを使えば1行ずつデータ処理できるので、書き方によってはメモリ節約可能です。
(データを全部PHP配列に追加したら同じ事はおきますが、PHP配列には入れずに出力してしまうなど)

https://codeigniter.jp/user_guide/3/database/results.html#id3

ただ、htmlに出力するのであればデータ量として多すぎるので、Freedom1abさんが回答されているようにLIMIT / OFFSETを使ったほうが良いと思います。

投稿2019/05/21 17:36

Eggpan

総合スコア2727

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

awazon78

2019/05/22 01:04

unbuffered_row()は知らなかったです!勉強になりました!ありがとうございます!
guest

0

ベストアンサー

下記のような「LIMIT」句が使えるdbですか?
SELECT col_name1 [, col_name2 ...] FROM table_name
LIMIT [offset,] row_count

LIMIT句が使えて、「その結果を配列に変換する際に件数が多くて発生」が原因なら
LIMIT句を使って回避できると思いますが・・・ページ区切りでLIMIT句を使った
経験しかないので、参考になれば幸いです。

投稿2019/05/21 04:55

編集2019/05/21 04:57
Freedom1ab

総合スコア60

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

awazon78

2019/05/21 05:07

使えます!やはりlimitですかね!
Freedom1ab

2019/05/21 05:18

ログをページ毎に表示する時に利用しましたが、 Offsetで飛ばす行数を指定したり、1ページの表示する行数をrow_countで指定して、ページング処理をロジックで書く手間の殆どをLIMIT句で書けて重宝しましたよ。
guest

0

SQL

1GROUP BY `user_id` 23GROUP BY `id`, `user_id`, `point`

では?

投稿2019/05/21 03:53

Orlofsky

総合スコア16415

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

test_point_historyのCREATE TABLEを見ると主キーもユニークもインデックスもつけられていないように見受けられますが、まずはそこからではないでしょうか。
expire_dateがnullになっていますが、もしnullばかり(たまにしか入らない)のであればテーブルを分割するなどの手段を考えたほうがいいかもしれません。

投稿2019/05/21 03:21

編集2019/05/21 03:24
m.ts10806

総合スコア80850

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

awazon78

2019/05/21 03:30

すいません、こちらはテストのテーブルで件数も少ないのでインデックスなど貼り忘れてましたが、実際には貼ってあります。
m.ts10806

2019/05/21 03:34

分かるように追記しておいてください。回答者には出されたものがすべてです。 今回のように質問が丁寧に書いてあってテストデータまで提示されていたら尚更ですね。
awazon78

2019/05/21 04:33

失礼いたしました
m.ts10806

2019/05/21 04:35

いえ、怒ってるわけでもなんでもないですよ。回答の方向性も決まるので追記してくださいとお願いしているだけです
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問