「テーブルから選択*」が悪い習慣と見なされる理由


96

昨日、私は「趣味」のプログラマーと話し合っていました(私自身はプロのプログラマーです)。私たちは彼の仕事のいくつかに出会い、彼は彼のデータベースのすべての列を(実動サーバー/コード上でも)常に照会すると言いました。

私は彼にそうしないように説得しようとしたが、まだそれほど成功していなかった。私の意見では、プログラマーは、「可愛さ」、効率、およびトラフィックのために実際に必要なものだけを照会する必要があります。私の見方に誤りがありますか?


1
テーブルの内容が変更された場合はどうでしょうか?列を追加/削除しますか?あなたはまだ* ..を選択しているので、必要なものを逃したり、必要以上のデータを引き戻したりすることになります。
JFそれは14

2
@JFitそれはその一部ですが、全体の話からはほど遠いです。
14



@gnat質問は本当に閉じられた質問の複製と見なすことができますか?(つまり、閉じられたものはそもそも本当に適切ではなかったため)
gbjbaanb 14

回答:


67

何を取り戻し、コード内の変数にどのようにバインドするかを考えてください。

次に、誰かがテーブルスキーマを更新して列を追加(または削除)したときに、直接使用していない列でもどうなるかを考えてみましょう。

クエリを手で入力するときにselect *を使用するのは問題ありません。コードのクエリを作成するときではありません。


8
パフォーマンス、ネットワーク負荷などは、列を目的の名前で順序どおりに戻す便利さよりもはるかに重要です。
14

21
@jwenting本当に?パフォーマンスは正確さよりも重要ですか?とにかく、必要な列だけを選択するよりも「select *」のほうが優れているとは思いません。
gbjbaanb

9
@Bratchは、実際の実稼働環境では、同じテーブルを使用する何百ものアプリケーションがあり、それらすべてのアプリケーションを適切に維持する方法はありません。あなたは感情的には正しいが、実際には、職場で働くという現実だけで議論は失敗する。アクティブなテーブルに対するスキーマの変更は常に発生します。
user1068 14

18
この答えの要点がわかりません。テーブルに列を追加すると、SELECT *とSELECT [Columns]の両方が機能します。唯一の違いは、コードを新しい列にバインドする必要がある場合、SELECT [Columns]を変更する必要があることです。 SELECT *はしません。列がテーブルから削除されると、SELECT *はバインドの時点で壊れますが、SELECT [Columns]はクエリの実行時に壊れます。テーブルの変更はバインディングの変更のみを必要とするため、SELECT *はより柔軟なオプションであるように思えます。何か不足していますか?
トールガイ14

11
次に、@ gbjbaanbは名前で列にアクセスします。クエリで列の順序を指定しない限り、それ以外のものは明らかに愚かになります。
イミビス

179

スキーマの変更

  • 順序によるフェッチ---データを取得する方法としてコードが列#をフェッチしている場合、スキーマの変更により列番号が再調整されます。これはアプリケーションを台無しにし、悪いことが起こります。
  • 名前で取得---コードがなどの名前で列を取得fooし、クエリ内の別のテーブルが列を追加するfoo場合、これを処理する方法は正しい foo列を取得しようとするときに問題を引き起こす可能性があります。

いずれにせよ、スキーマの変更はデータの抽出に問題を引き起こす可能性があります。

さらに、使用されていた列がテーブルから削除されているかどうかを検討してください。select * from ...結果セットからデータをプルしようとしたとき、まだ出て動作しますが、エラー。クエリで列が指定されている場合、クエリはエラーになり、代わりに何がどこに問題があるかを明確に示します。

データオーバーヘッド

一部の列には、大量のデータが関連付けられている場合があります。戻る*を選択すると、すべてのデータが取得されます。ええ、varchar(4096)これは、選択した1000行にあり、必要のない追加の4メガバイトのデータを提供しますが、とにかく回線を介して送信されます。

スキーマの変更に関連して、最初にテーブルを作成したときにそのvarcharが存在していなかった場合がありますが、現在はそこにあります。

意図を伝えられない

戻るを選択して*20列を取得し、そのうち2列のみが必要な場合、コードの意図を伝えていません。クエリを実行すると、クエリselect *の重要な部分が何であるかがわかりません。これらの列を含めないことで高速化するために、代わりにこの他のプランを使用するようにクエリを変更できますか?クエリが返す内容の意図が明確でないため、わかりません。


これらのスキーマの変更をもう少し探求するいくつかのSQLフィドルを見てみましょう。

最初に、初期データベース:http : //sqlfiddle.com/#!2 / a67dd/1

DDL:

create table one (oneid int, data int, twoid int);
create table two (twoid int, other int);

insert into one values (1, 42, 2);
insert into two values (2, 43);

SQL:

select * from one join two on (one.twoid = two.twoid);

そして、あなたが戻って取得列はoneid=1data=42twoid=2、とother=43

さて、表1に列を追加するとどうなりますか?http://sqlfiddle.com/#!2/cd0b0/1

alter table one add column other text;

update one set other = 'foo';

そして、前と同じクエリからの私の結果はoneid=1data=42twoid=2、とother=foo

テーブルの1つを変更するselect *とaの値が乱れ、突然「other」をintにバインドするとエラーがスローされ、その理由はわかりません。

代わりに、SQLステートメントが

select 
    one.oneid, one.data, two.twoid, two.other
from one join two on (one.twoid = two.twoid);

テーブル1を変更しても、データが混乱することはありません。そのクエリは、変更前と変更後に同じように実行されます。


索引付け

を実行すると、条件に一致するすべてのテーブルからすべての行select * fromがプルされます。あなたが本当に気にしないテーブルですら。これは、より多くのデータが転送されることを意味しますが、スタックのさらに下に潜む別のパフォーマンスの問題があります。

インデックス。(SOに関連:selectステートメントでインデックスを使用する方法?

多数の列をプルバックする場合、データベースプランオプティマイザーインデックスの使用を無視する場合があります。とにかくすべての列をフェッチする必要があり、インデックスを使用してからクエリのすべての列をフェッチするのに時間がかかるためです。完全なテーブルスキャンを実行するだけの場合よりも。

たとえば、ユーザーの姓(多くの操作を行い、インデックスを付ける)を選択している場合、データベースはインデックスのみのスキャンを実行できます(postgres wiki index only scanmysql full table scan vs fullインデックススキャンインデックスのみのスキャン:テーブルアクセスの回避)。

可能であれば、インデックスからのみ読み取ることについてかなりの最適化があります。情報を各インデックスページにすばやく取り込むことができます。これは、取得する情報が少ないためですselect *。インデックスのみのスキャンでは、100倍の速度で結果を返すことが可能です(ソース:Select * is bad)。

これは、完全なインデックススキャンが優れていると言っているのではなく、依然として完全なスキャンですが、完全なテーブルスキャンよりも優れています。select *パフォーマンスを損なうすべての方法を追いかけ始めたら、新しい方法を見つけ続けます。

関連読書


2
@Tonny同意します-しかし、(最初に)答えたとき、この質問が非常に多くの議論と解説を生み出すとは思いませんでした!名前付き列のみを照会するのは明らかですよね!
gbjbaanb 14

3
列を追加することで、すべてを破ることも、コードは常にハードコードされた序数でない名前でのDataReaderの列にアクセスしなければならない正当な理由です...
ジュリア・ヘイワード

1
@gbjbaanbそれは私です。しかし、多くの人が正式なバックグラウンド/トレーニングなしでSQLクエリを書くようになります。彼らにとっては明らかではないかもしれません。
トニー14

1
@Aaronaughtインデックス作成の問題に関する追加情報で更新しました。私が間違っていることを提起すべき他のポイントはありますselect *か?

3
うわー、受け入れられた答えは、実際に何かを説明するのが非常に貧弱だったので、私はそれを投票しました。これが受け入れられた答えではないことに驚いた。+1。
ベン・リー

38

別の懸念:JOINクエリであり、クエリ結果を連想配列に取得する場合(PHPの場合など)、バグが発生しやすいです。

事はそれです

  1. テーブルfooに列がidあり、name
  2. テーブルbarに列がidありaddress
  3. そしてあなたのコードであなたが使用しています SELECT * FROM foo JOIN bar ON foo.id = bar.id

誰かがテーブルに列nameを追加するとどうなるかを推測してbarください。

name列が結果に2回表示されるようになり、結果を配列に保存している場合、2番目namebar.name)のデータが最初のnamefoo.name)を上書きするため、コードは突然正常に動作しなくなります!

それは非常に明白ではないため、非常に厄介なバグです。把握するのに時間がかかる場合があり、テーブルに別の列を追加する人がそのような望ましくない副作用を予想することはできません。

(実話)。

したがって、を使用しないでください*。取得する列を制御し、必要に応じてエイリアスを使用してください。


このケースでは大丈夫です(これはまれなことだと考えています)。これは大きな問題になる可能性があります。ただし、ワイルドカードを使用してクエリを実行し、同じ列名のエイリアスを追加するだけで、それを回避できます(ほとんどの人はおそらくそうするでしょう)。
ベーコン14

4
理論的には、利便性のためにワイルドカードを使用する場合、既存のすべての列を自動的に提供するためにワイルドカードを使用し、テーブルの成長に合わせてクエリを更新する必要はありません。すべての列を指定している場合は、クエリに移動してSELECT句に別の列を追加する必要があります。これは、名前が一意でないことを願うときです。ところで、大規模なデータベースを備えたシステムではそれほど珍しいとは思いません。私が言ったように、私はかつてPHPコードの大きな泥玉でこのバグを捜すのに数時間を費やしました。そして今、別のケースを見つけました:stackoverflow.com/q/17715049/168719
Konrad Morawski 14

3
私は先週、コンサルタントの頭を介してこれを取得しようとして1時間を費やしました。彼はため息... ... SQLの第一人者であると考えられる
Tonny

22

多くの場合、すべての列を照会することは完全に合法です。

すべての列を常に照会することはそうではありません。

データベースエンジンにとっては、より多くの作業が必要になります。実際にデータを取得して送り返すという実際のビジネスに取りかかる前に、内部のメタデータを調べて、どの列を処理する必要があるかを調べなければなりません。OK、これは世界最大のオーバーヘッドではありませんが、システムカタログはかなりのボトルネックになる可能性があります。

1つまたは2つのフィールドだけが必要な場合に任意の数のフィールドをプルバックするため、ネットワークにとってはより多くの作業が必要になります。他の人が数十の余分なフィールドを追加し、そのすべてに大きなテキストのチャンクが含まれる場合、スループットは突然床を通過します-明白な理由はありません。これは、 "where"句が特に適切でなく、大量の行を引き戻している場合、さらに悪化します。これは、潜在的に大量のデータがネットワークを介してユーザーに送信されることです(つまり、低速になります)。

おそらく気にしないこの余分なデータをすべて引き戻して保存する必要があるため、アプリケーションにとってはより多くの作業が必要になります。

列の順序が変わるリスクがあります。OK、これについて心配する必要はありません(必要な列のみを選択する場合は気にしません)が、一度にそれらをすべて取得し、誰かがテーブル内の列の順序を再配置することに決めた場合、慎重に作成された、ホールの下のアカウントに提供するCSVエクスポートは、突然すべてがポットに移動します。

ところで、私は「誰か」と数回言った。データベースは本質的にマルチユーザーであることを忘れないでください。あなたがあなたがそう思うと思うそれらを制御できないかもしれません。


3
スキーマに依存しないテーブル表示機能など、すべての列を常にクエリすることは正当であると思います。それほど一般的な状況ではありませんが、内部使用専用ツールのコンテキストでは、このようなことは便利です。
supercat

1
@supercatこれは、私が考えることができる「SELECT *」の唯一の有効なユースケースです。そしてそれでも、クエリを "SELECT TOP 10 *"(MS SQL)に制限するか、 "LIMIT 10"(mySQL)を追加するか、 "WHERE ROWNUM <= 10"(Oracle)を追加することを好みます。通常、その場合は、完全なコンテンツというよりは、「どのような列があり、いくつかのサンプルデータがある」ということになります。
トニー14

@Tonny:SQL Serverはデフォルトスクリプトを変更してTOP制限を追加しました。コードが表示したい数を読み取り、クエリを破棄する場合、それがどれほど重要かはわかりません。詳細はわかりませんが、クエリの応答は多少遅延して処理されると思います。いずれにせよ、「合法ではない」と言うよりも、「...合法的にははるかに少ない」と言う方が良いと思います。基本的に、正当なケースは、ユーザーがプログラマーよりも意味のあるものをよりよく理解できるケースとして要約します。
supercat 14

@supercat私はそれに同意することができます。そして、私はあなたがあなたの最後の文にそれを置く方法が本当に好きです。覚えておく必要があります。
トニー14

11

簡単な答えは、使用するデータベースによって異なります。リレーショナルデータベースは、必要なデータを高速で信頼性の高いアトミックな方法で抽出するために最適化されています。大規模なデータセットや複雑なクエリでは、SELECT *よりもはるかに高速で安全であり、「コード」側での結合と同等の処理を行います。Key-Valueストアには、このような機能が実装されていないか、実稼働で使用できるほど成熟していない場合があります。

つまり、使用しているデータ構造をSELECT *で設定し、残りをコード内で解決することはできますが、拡張したい場合はパフォーマンスのボトルネックが見つかります。

最も近い比較はデータの並べ替えです。クイックソートまたはバブルソートを使用でき、結果が正しくなります。しかし、最適化されず、同時実行性を導入し、アトミックにソートする必要がある場合、間違いなく問題が発生します。

もちろん、SQLクエリを実行できるプログラマーに投資するよりもRAMとCPUを追加する方が安価で、JOINとは何なのかを理解することさえできます。


SQLを学ぶ!そんなに難しくありません。これは、広範囲にわたるデータベースの「ネイティブ」言語です。強力です。エレガントです。それは時の試練に耐えてきました。また、SQL結合の実行に本当に慣れていない限り、データベースでの結合よりも効率的な「コード」側で結合を作成する方法はありません。「コード結合」を行うには、単純な2つのテーブル結合でも、両方のテーブルからすべてのデータをプルする必要があることを考慮してください。または、インデックスの統計情報を取得し、それらを使用して、結合する前に取得するテーブルデータを決定しますか?そうは思いませんでした...データベースを正しく使用する方法を学んでください。
クレイグ14

@Craig:SQLは、リレーショナルデータベースで広く使用されています。しかし、これは唯一のタイプのDBにはほど遠い...そして、より現代的なデータベースアプローチがしばしばNoSQLと呼ばれる理由があります。:P皮肉をかけずにSQLを「エレガント」と呼ぶ人はいないでしょう。リレーショナルデータベースに関する限り、他の多くの選択肢よりも少ない量です。
cHao 14

@cHao 何十年もの、他のさまざまな種類のデータベースを非常によく知っています。Pick "nosql"データベースは永遠に存在しています。「NoSQL」は、リモートでも新しい概念ではありません。ORMも永遠に存在し、常に低速でした。遅い!=良い。:優雅(?LINQ)として、あなたは、where句のために、これは合理的かエレガントで私を納得させることはできませんCustomer customer = this._db.Customers.Where( “it.ID = @ID”, new ObjectParameter( “ID”, id ) ).First();を参照してくださいオフェンス取る時間 2ページを
クレイグ・

@Craig:ORMを始めてはいけません。そこにあるほとんどすべてのシステムは恐ろしくそれをします、そして抽象化はあちこちに漏れます。だリレーショナルDBのレコードはオブジェクトではありません -最高の状態で、彼らは、オブジェクトの一部のシリアル化可能な根性です。しかし、LINQに関しては、本当にそこに行きたいですか?同等のSQLishはvar cmd = db.CreateCommand(); cmd.CommandText = "SELECT TOP 1 * FROM Customers WHERE ID = @ID"; cmd.Parameters.AddWithValue("@ID", id); var result = cmd.ExecuteReader();....のようなもので、次に各行からCustomerを作成します。LINQはそのズボンを打ち負かします。
cHao 14

@クレイグ:確かに、それはそれができるほどエレガントではありません。しかし、.netコードをSQLに変換できるまで、私が望むほどエレガントになることはありません。:)その時点で言うことができますvar customer = _db.Customers.Where(it => it.id == id).First();
cHao 14

8

IMO、明示的か暗黙的かについて。コードを書くとき、すべての部分がたまたまそこにあるからというだけでなく、コードを機能させたので、機能させたいのです。すべてのレコードを照会し、コードが機能する場合、先に進む傾向があります。後で何かが変更されてコードが機能しなくなった場合、多くのクエリや関数をデバッグするのは大変な苦労であり、そこにあるべき値を探し、値の参照は*のみです。

また、N層アプローチでは、データベーススキーマの中断をデータ層に分離するのが最善です。データ層がビジネスロジックに*を渡し、ほとんどの場合プレゼンテーション層で渡される場合、デバッグ範囲を指数関数的に拡張しています。


3
これはおそらくここで最も重要な理由の1つであり、票のほんの一部を獲得しています。散らかったコードベースの保守性select *ははるかに悪いです!
イーモンネルボンヌ14

6

テーブルに新しい列が追加されると、不要な列もすべて取得されるためです。でvarchars、このDBから移動する必要余分なデータの多くなることができます

一部のDB最適化では、固定長部分へのアクセスを高速化するために、select *を使用して固定長でないレコードを別のファイルに抽出することもありますが、その目的は無効になります。


1

オーバーヘッドとは別に、そもそも避けたいことは、プログラマーとして、データベース管理者が定義した列の順序に依存しないと言えます。すべてが必要な場合でも、各列を選択します。


3
同意しますが、どのような場合でも列名で結果セットから値を引き出すこともお勧めします。
ロリーハンター

出向、持ち運び。列名を使用します。列の順序に依存しません。列の順序は脆弱な依存関係です。名前は、実際の設計作業から派生したものである必要があります(クエリで複合列または計算または競合する列名を明示的にエイリアスし、指定した明示的なエイリアスを参照します)。しかし、順番に頼ることは...ちょうどダクトかなりのテープと祈りである
クレイグ

1

データベースのすべての列を取得するという目的でビルドを使用しない理由はわかりません。次の3つのケースがあります。

  1. データベースに列が追加され、コードでも必要になります。a)With *は適切なメッセージで失敗します。b)*がなくても動作しますが、期待どおりの動作をしません。

  2. データベースに列が追加されますが、コードには必要ありません。a)With *は失敗します。つまり、セマンティクスは「すべてを取得する」ことを意味するため、*は適用されなくなります。b)*なしで機能します。

  3. 列が削除されましたコードはどちらの方法でも失敗します。

現在、最も一般的なケースはケース1です(*を使用しているため、おそらくすべてが必要です)。*がなければ、正常に動作するが期待どおりの動作をしないコードを作成できます

私の意見ではエラーが発生しやすい列インデックスに基づいて列データを取得するコードを考慮していません。列名に基づいて取得するのは、はるかにロジックです。


あなたの前提は間違っています。Select *は、アプリケーション開発目的ではなく、アドホッククエリの利便性を目的としています。またはselect count(*)、クエリエンジンがインデックスを使用するかどうか、使用するインデックスなどを決定できるようにする統計構造で使用し、実際の列データを返さないようにします。または、のような句で使用する場合もwhere exists( select * from other_table where ... )、クエリエンジンが独自に最も効率的なパスを選択するように誘い、サブクエリはメインクエリからの結果を制約するためにのみ使用されます。など
クレイグ

@Craig SQLのすべての本/チュートリアルにselect *は、すべての列を取得するというセマンティクスがあります。アプリケーションでこれが本当に必要な場合は、使用しない理由はわかりません。select *ビルドの目的がすべての列を取得することではないことについて言及しているリファレンス(Oracle、IBM、Microsoftなど)を指すことができますか?
m3th0dman 14

もちろん、select *すべての列を取得するために存在します...アドホッククエリのための便利な機能として、実稼働ソフトウェアでの素晴らしいアイデアではありません。理由はこのページの回答ですでに十分に説明されているため、独自の詳細な回答を作成しませんでした。 •)を選択限られたケースで、クエリプランの最適化の失敗(いくつかのケースでは、インデックスを使用するために失敗)、•)非効率的なサーバのI / Oを持つことができるだけで使用されるインデックスなどを
クレイグ

select *実際の運用アプリケーションでの使用を正当化するエッジケースがあるかもしれませんが、エッジケースの性質は、一般的なケースではないということです。:-)
クレイグ14

@Craig理由は、使用することではなく、データベースからすべての列を取得することselect *です。すべての列が本当に必要な場合に私が言っていたことは、あなたが使用すべきではない理由はわかりませんselect *。ただし、すべての列が必要なシナリオはほとんどありません。
m3th0dman 14

1

このように考えてみてください...少数の小さな文字列または数値フィールドのみを持つテーブルからすべての列をクエリすると、合計100kのデータになります。悪い練習ですが、実行されます。ここで、たとえば、画像または10 MBのワードドキュメントを保持する単一のフィールドを追加します。テーブルにフィールドが追加されたという理由だけで、高速なクエリがすぐに実行され、不思議なことにパフォーマンスが低下し始めSelect * from Tableます。


6
これは単に、既に数時間前に作られたポイントを繰り返しているようだ最初の答えと他の回答のカップルで
ブヨ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.