SQLServerのManualPagingについて

63 views
Skip to first unread message

志水正幸

unread,
Oct 24, 2019, 1:44:24 AM10/24/19
to DBFluteユーザの集い
志水です。
いつもお世話になっております。

数年前にDBFlute.NETを使用して作成した
アプリケーションなのですが
現行はORACLE11gで動いているものを
SQLServer(開発環境なのでバージョンは2008とちょっと古いです)へ移行中です。

現在、外部SQLのManualPagingの箇所を変更しているのですが
初回の1頁目分のレコードは取得できるのですが
2頁目のデータを取ろうとした際にデータを取得できない事象
でハマっています。
SQLServerの場合にDBFluteのManualPaging関連の設定で
変更しないといけないような箇所とかありますか


以下、状況を確認した内容です。
1.ログに出力されているSQLは2頁目を取得可能な正しいSQLが発行されています。
SQLはマニュアルを参考に修正し、2頁目を取得時のSQLでは10件目~取得するような
形でSQLは発行されていました。
select * from (select plain.*, row_number() over(order by DENPYO_NO DESC, DENPYO_KBN ASC) as rn from (
  :
  :
  :
) plain
) ext
 where ext.rn > 10
   and ext.rn <= 20

2.PagingResultBeanをデバックで確認したのですが2頁目ではselectedListにはデータが入ってきませんでした。
そのため、>>    if (pagingBean.CanPagingReSelect && IsNecessaryToReadPageAgain(rb)) { 内の処理に
入り最終頁まで処理が繰り返していようです。
結果的に、画面上は最終頁が表示された状態でデータはゼロ件です。


        public PagingResultBean<ENTITY> InvokePaging(PagingHandler<ENTITY> handler) {
            AssertObjectNotNull("handler", handler);
            PagingBean pagingBean = handler.PagingBean;
            AssertObjectNotNull("handler.getPagingBean()", pagingBean);
            if (!pagingBean.IsFetchScopeEffective) {
                String msg = "The paging bean is not effective about fetch-scope!";
                msg = msg + " When you select page, you should set up fetch-scope of paging bean(Should invoke fetchFirst() and fetchPage()!).";
                msg = msg + " The paging bean is: " + pagingBean;
                throw new SystemException(msg);
            }
            int allRecordCount;
            IList<ENTITY> selectedList;
            if (_countLater) { // not implemented about performance optimization
                selectedList = handler.Paging();
                allRecordCount = handler.Count();
            } else {
                allRecordCount = handler.Count();
                selectedList = handler.Paging();
            }
            PagingResultBean<ENTITY> rb = new ResultBeanBuilder<ENTITY>(_tableDbName).BuildPagingResultBean(pagingBean, allRecordCount, selectedList);
            if (pagingBean.CanPagingReSelect && IsNecessaryToReadPageAgain(rb)) {
                pagingBean.FetchPage(rb.AllPageCount);
                int reAllRecordCount = handler.Count();
                IList<ENTITY> reSelectedList = handler.Paging();
                return new ResultBeanBuilder<ENTITY>(_tableDbName).BuildPagingResultBean(pagingBean, reAllRecordCount, reSelectedList);
            } else {
                return rb;
            }
        }


3.


kubo

unread,
Oct 24, 2019, 2:05:21 AM10/24/19
to DBFluteユーザの集い
jfluteです

志水さん、こんにちは

> SQLServerの場合にDBFluteのManualPaging関連の設定で
> 変更しないといけないような箇所とかありますか?

ふむ、基本的にはSQLさえ変えてしまえば、他は移行の必要はないはずですが...
状況をもう少し確認させてください。

> そのため、>> if (pagingBean.CanPagingReSelect && IsNecessaryToReadPageAgain(rb)) { 内の処理に
> 入り最終頁まで処理が繰り返していようです。

2ページ目のときに...

o row_number()のSQLは想定通りの結果 (10件目以降のデータが取得できる)
 => SQLを直接SQLServerに実行した場合
o selectList()の戻り値の PagingResultBean が空っぽ
o InvokePaging() のリトライ機能が動いている (最終ページが検索される)

という感じでしょうかね?

リトライ機能が動いているということは、そのSQLで結果が0件と判定されているので、
SQLでは数件ちゃんと取れるのに、その時点で0件となるのと、その間がおかしいということになりまmす。

志水正幸

unread,
Oct 24, 2019, 6:51:01 AM10/24/19
to DBFluteユーザの集い
志水です。

jfluteさんこんばんは。。

早速のお返事ありがとうございます。

>ふむ、基本的にはSQLさえ変えてしまえば、他は移行の必要はないはずですが...
-- ELSE select count(*) FROM (select 1 FROM t_ukagai wk/*END*/
↓↓↓↓↓
-- ELSE select count(*) AS CNT FROM (select 1 AS CNT2 FROM t_ukagai wk/*END*/
SQLですが、COUNT取る部分のSELECT句には別名付けてあげないとダメなんですね。
ORACLEでは必要なかったので、気付くのに1日試行錯誤してました。。。
この別名付けたことでって事はないですよね・・・1頁目は動いてるし。


>o row_number()のSQLは想定通りの結果 (10件目以降のデータが取得できる) 
> => SQLを直接SQLServerに実行した場合 
ログで吐き出している2ページ目のSQLを直接実行すると想定通りデータは取れてきています。

>o selectList()の戻り値の PagingResultBean が空っぽ 
そうです。SelectCountはゼロ件でした。

>o InvokePaging() のリトライ機能が動いている (最終ページが検索される) 
何度かトライして、そのたびにページカウントが上がっているようですね。。
最終的に1070~1080件を取るようなSQLで終わっていました。


>リトライ機能が動いているということは、そのSQLで結果が0件と判定されているので、
>SQLでは数件ちゃんと取れるのに、その時点で0件となるのと、その間がおかしいということになりまmす。
ORACLEではちゃんと動いているので、SQLServerの場合に何か設定があるかと思ったのですが・・・

他に何か確認する箇所とかありますでしょうか?

以上、宜しくお願い致します。

kubo

unread,
Oct 24, 2019, 8:49:51 AM10/24/19
to DBFluteユーザの集い
jfluteです

> >o selectList()の戻り値の PagingResultBean が空っぽ
> そうです。SelectCountはゼロ件でした。

あっ、もうちょい整理させてください。
2ページめのページング検索で、SQLは2回流れると思います。

一つは実データ検索、もう一つはカウント検索、
ログのSQL的には、どちらも想定どおりですか? (値がちゃんと取れる、件数も0じゃない)

> >o InvokePaging() のリトライ機能が動いている (最終ページが検索される)
> 何度かトライして、そのたびにページカウントが上がっているようですね。。
> 最終的に1070~1080件を取るようなSQLで終わっていました。

ふむぅ、ここがちょっとおかしいですね。。。

外だしSQLを業務情報伏せた上で、まるごと載せて頂けないでしょうか?

志水正幸

unread,
Oct 24, 2019, 8:48:56 PM10/24/19
to DBFluteユーザの集い
志水です。

jfluteさん。おはようございます。


リトライのところですが
再度確認したのですが2頁目が取れなかった後、すぐに最終頁の取得になってました。
何度かと思ったのはデータ存在のチェックと実データ取得の処理が繰り返されるているのを
てっきりカウントアップしていると勘違いしていたようでした。


>外だしSQLを業務情報伏せた上で、まるごと載せて頂けないでしょうか? 
外だしSQLと実行後のSQLを載せた「外部SQL.txt」を添付します。



以上、宜しくお願い致します。




2019年10月24日木曜日 21時49分51秒 UTC+9 jflute:
外部SQL .txt

kubo

unread,
Oct 24, 2019, 10:31:13 PM10/24/19
to DBFluteユーザの集い
jfluteです

ありがとうございます。

where ext.rn > 1070
and ext.rn <= 1080

最終ページのreselectが行われているということは...
2ページ目のカウント検索は1080件くらい、でも実データ検索の検索結果は0件、
ということになってるんじゃないかと推測されます。
(isNecessaryToReadPageAgain()の判定から)

protected boolean isNecessaryToReadPageAgain(PagingResultBean<ENTITY> rb) {
return rb.getAllRecordCount() > 0 && rb.getSelectedList().isEmpty();
}

最終ページのreselectのSQL自体は原因ではないので、
reselectされる前の2ページ目のSQLたちを検証してみると良いと思います。

Oracleでちゃんと動いていたということは、C#のコードではなく、
SQL自体に何か特徴があるんじゃないかというひとまずの推測です。


あと、SQLを見ると...カウント検索と実データ検索のSQLが違いがアリすぎるような???
通常は、select句とrownumberのext.rnの部分だけ違くて後はほぼ同じじゃないといけないですけど。

志水正幸

unread,
Oct 25, 2019, 2:05:52 AM10/25/19
to DBFluteユーザの集い
志水です。

fluteさん、こんにちは。。


>Oracleでちゃんと動いていたということは、C#のコードではなく、 
>SQL自体に何か特徴があるんじゃないかというひとまずの推測です。
ログで吐き出されたSQLをSQLツールで実行すると問題なくデータは取得できています

>あと、SQLを見ると...カウント検索と実データ検索のSQLが違いがアリすぎるような??? 
>通常は、select句とrownumberのext.rnの部分だけ違くて後はほぼ同じじゃないといけないですけど。 
もうかれこれ6、7年前に作ったやつで仕様書も作ってないので忘れてしまってるのですが
気持ち悪いので作り直して実行してみました。
残念ながら結果は同じでした。

DBがわるいんですかねぇ・・・
シノニムも使えないし、、

以上、宜しくお願い致します。
再作成した外部SQL.txt

kubo

unread,
Oct 25, 2019, 2:15:48 AM10/25/19
to DBFluteユーザの集い
jfluteです

> 気持ち悪いので作り直して実行してみました。
> 残念ながら結果は同じでした。

ふむぅ、SQLを直してもダメですか...

とにかく、ログに出力されているSQLはすべて期待通りの結果になるわけですね?
であれば、どこか仕組みの部分でSQLServerと相性が悪くなっているとしか考えられないので...
C#だと allcommon の配下にすべて仕組みのコードもあるので、
どの部分で0件になっちゃってるか追ってみると良いと思います。

kubo

unread,
Oct 25, 2019, 5:22:54 AM10/25/19
to DBFluteユーザの集い
jfluteです

少し整理してみましたが...

o 1ページ目のPagingResultBeanは想定通り (データが取れる)
o 2ページ目のPagingResultBeanは想定外の0件 (データが取れるはずなのに)
 - ログの「実データSQL」と「カウントSQL」を直接実行すると想定通り (両方とも)
 - InvokePaging() の selectedList は0件 (allRecordCountは?)

「実データの件数」と「総レコード件数」という二つの値があって、
両方とも想定外なのか?片方だけ想定外なのか?区別したほうが良さそうですね。


InvokePaging()メソッドの、以下の該当箇所、
2ページ目の検索時は selectedList と allRecordCount は
何になっていますでしょうか?

if (_countLater) { // not implemented about performance optimization
selectedList = handler.Paging();
allRecordCount = handler.Count();
} else {
allRecordCount = handler.Count();
selectedList = handler.Paging();
}

最終ページの再検索に処理が行っているのは、
2ページ目のSQLが何かしら矛盾を起こしていることから起きる副次的な現象なので、
原因はその前の部分に着目した方が良さそうですね。

志水正幸

unread,
Oct 27, 2019, 10:13:05 PM10/27/19
to DBFluteユーザの集い
志水です。

返信遅くなりました。
添付のとおりの結果でした。
総件数(allRecordCount )は1075件ですが
selectedList はゼロ件でした。
while(dataReader.Read())の時点で確認すると
この時点でデータは取得できていないようです。


以上、宜しくお願い致します。
selectedList.png
dataReader.png
allRecordCount.png

kubo

unread,
Oct 27, 2019, 10:39:02 PM10/27/19
to DBFluteユーザの集い
jfluteです

おお、かなり絞れてきましたね。

総件数自体は正しく取れていて、
1075件だから最終ページ検索が1070から1080までのページング条件なのですね。

焦点は、ページング条件の入った実データ検索となりそうですね。
2ページ目ではなく最終ページ検索にしたって、データは5件取れていいはずですものね。

o 実データ検索のSQLは、コピーして直接SQLServerで実行するとデータは取得できる
o 実データ検索のselectedListがなぜか0件になる
o 実データ検索のdataReader.Read()の時点でデータ取得できてないかも


ひとつ確認です。while(dataReader.Read()) のループは回ってます?

例えば、ループは回ってるけど途中のif文で想定外の分岐をして実質0件になっているとかであれば、
実はデータは取れているんだけど、select句の構成で不具合があったり。

もしくは、ループ自体が回っていないのであれば、DBFlute.NET じゃなく、
ADO.NETレベルでそのSQLがデータが取得できない要因があるかもなので、
プレーンなADO.NETで実行するサンプルを作ってみて比較してみたほうが良かったり。

と考えられそうです。
Message has been deleted

志水正幸

unread,
Oct 28, 2019, 2:08:40 AM10/28/19
to DBFluteユーザの集い



>ひとつ確認です。while(dataReader.Read()) のループは回ってます? 
>例えば、ループは回ってるけど途中のif文で想定外の分岐をして実質0件になっているとかであれば、 
>実はデータは取れているんだけど、select句の構成で不具合があったり。 
while(dataReader.Read())の時点で取得できていません。
ループの中に入っていません。
ログ出力されている箇所はあったのですが
実際に「@1」とかのパラメータ変換後の実行しているSQLが見たくてデバッグで追っていたのですが
結局わからなかったです。どこかで隠ぺいされて実行されているんですかね?


>もしくは、ループ自体が回っていないのであれば、DBFlute.NET じゃなく、 
ADO.NETレベルでそのSQLがデータが取得できない要因があるかもなので、 
>プレーンなADO.NETで実行するサンプルを作ってみて比較してみたほうが良かったり。 

            String sql = "-- #UkagaiKensaku#\r\n\r\n-- !df:pmb extends Paging!\r\n-- !!String Kaisha_cd!!\r\n-- !!String Kaikei_cd!!\r\n-- !!String Nendo!!\r\n-- !!longQ Denpyo_no_ST!!\r\n-- !!longQ Denpyo_no_ED!!\r\n-- !!intQ Denpyo_kbn!!\r\n-- !!String Kian_user!!\r\n-- !!DateTimeQ Kian_ymd_ST:fromDate!!\r\n-- !!DateTimeQ Kian_ymd_ED:toDate!!\r\n-- !!intQ Kessai!!\r\n\r\n\r\nselect * from (select plain.*, row_number() over(order by DENPYO_NO DESC, DENPYO_KBN ASC) as rn from (\r\n\r\nSelect \r\nA.DENPYO_NO,\r\nA.DENPYO_KBN,\r\nD.DENPYO_KBN_NM,\r\nA.KARIKATA_KAMOKU_NM,\r\nA.KASIKATA_KAMOKU_NM,\r\nA.TEKIYO_1,\r\nA.KIAN_YMD,\r\nA.KIAN_USER,\r\nE.SYOKUIN_NM,\r\nA.KESSAI,\r\nA.KESSAI_YMD,\r\nA.KESSAI_JIKKO_YMD,\r\nA.SEKOU_YMD,\r\nC.SUMKINGAKU,\r\nA.SIWAKE_DENPYO_NO,\r\nA.VERSION_NO \r\nFROM T_UKAGAI A \r\nLEFT OUTER JOIN M_SYOKUIN E ON (A.Kaisha_CD = E.Kaisha_CD AND A.KIAN_USER = E.SYOKUIN_ID) , M_DENPYOKBN D, \r\n(SELECT Kaisha_CD,KAIKEI_CD,NENDO,DENPYO_NO,SUM(KARIKATA_KIN) AS SUMKINGAKU FROM T_UKAGAI WK \r\n\r\nWHERE \r\nWK.Kaisha_CD = '2391'\r\nAND WK.KAIKEI_CD = '01'\r\nAND WK.NENDO = '2017'\r\n\r\n\r\n\r\n\r\nAND WK.KIAN_YMD >= '2017-04-01'\r\nAND WK.KIAN_YMD < '2018-04-01'\r\nAND WK.KESSAI = 1\r\n\r\nGROUP BY WK.Kaisha_CD,WK.KAIKEI_CD,WK.NENDO,WK.DENPYO_NO) C\r\n\r\nWHERE \r\nexists\r\n(SELECT Kaisha_CD,KAIKEI_CD,NENDO,DENPYO_NO FROM T_UKAGAI B \r\nWHERE\r\nA.Kaisha_CD = B.Kaisha_CD \r\nAND A.KAIKEI_CD = B.KAIKEI_CD \r\nAND A.NENDO = B.NENDO \r\nAND A.DENPYO_NO = B.DENPYO_NO \r\nAND A.DENPYO_ROW = 1 \r\nAND A.DENPYO_KBN = B.DENPYO_KBN\r\nAND B.Kaisha_CD = '2391'\r\nAND B.KAIKEI_CD = '01'\r\nAND B.NENDO = '2017'\r\n\r\n\r\n\r\n\r\nAND B.KIAN_YMD >= '2017-04-01'\r\nAND B.KIAN_YMD < '2018-04-01'\r\nAND B.KESSAI = 1\r\nGROUP BY B.Kaisha_CD,B.KAIKEI_CD,B.NENDO,B.DENPYO_NO) \r\nAND \r\nA.Kaisha_CD = C.Kaisha_CD AND\r\nA.KAIKEI_CD = C.KAIKEI_CD AND\r\nA.NENDO = C.NENDO AND \r\nA.DENPYO_NO = C.DENPYO_NO AND \r\nA.Kaisha_CD = D.Kaisha_CD AND\r\nA.DENPYO_KBN = D.DENPYO_KBN \r\n\r\n) plain\r\n) ext\r\n\r\n where ext.rn > 0\r\n   and ext.rn <= 10";
            SqlConnection conn = new SqlConnection();
            SqlCommand command = new SqlCommand();
            SqlDataAdapter adapter = new SqlDataAdapter();
            DataSet ds = new DataSet();

            conn.ConnectionString = @"Data Source=localhost;Initial Catalog=DB;User Id=user;Password=pass;";

            command.Connection = conn;
            command.CommandText = sql;

            adapter.SelectCommand = command;
            adapter.Fill(ds);

            foreach (DataRow row in ds.Tables[0].Rows)
            {
                Console.WriteLine("id: {0}  name: {1}", row["DENPYO_NO"], row["DENPYO_KBN"]);
            }


以上、宜しくお願い致します。

2019年10月28日月曜日 11時39分02秒 UTC+9 jflute:

kubo

unread,
Oct 28, 2019, 2:18:40 AM10/28/19
to DBFluteユーザの集い
jfluteです

なるほど,DataReaderが一ループもしないわけですね。
一方で、ADO.NET直接だとちゃんとデータは取れると。

したら、もうその間で何かが起きているとしか言いようがないので、
while(dataReader.Read()) の前をどんどん追っていくってところですが...

もう一つ検証してみると良いと思うのが、
絞り込み条件とか全くなくバインド変数も何も使わないで、
ページングの条件とかSQLの全体構成だけそのままの外だしSQLにして、
いま一度実行してデバッグしてみるといいかもです。

何かしら、条件がヒットしなくなるような処理がされてしまっているとか。
まあ、ログのSQLではヒットしているようなので考えにくいですが、
ログのSQLは人間が見やすいようにバインド変数を埋め込みにしていますので、
厳密にはそのログのSQLが実行されているわけではないので。

kubo

unread,
Oct 28, 2019, 2:35:56 AM10/28/19
to DBFluteユーザの集い
jfluteです

ソースコードの追っかけですが、

S2DaoMetaDataExtension で new されている
「FetchNarrowingResultSetFactory」をデバッグしてみると良いかもしれません。
そこで何か起きてないかなぁとか。

順番的にこんな感じで、dataReaderのwhileループまで実行されているはずです。
(たぶん...ConditionBeanとか外だしSQLとか内容次第で色々と分岐はするのですが)

S2DaoMetaDataExtension // with new FetchNarrowingResultSetFactory()
 |-S2DaoSelectDynamicCommand
  |-InternalBasicSelectHandler
   |-InternalBeanListMetaDataResultSetHandler

志水正幸

unread,
Oct 28, 2019, 4:09:45 AM10/28/19
to DBFluteユーザの集い
志水です。


2件目のとき、下記の処理の
                while (_fetchCounter < skipStartIndex && _dataReader.Read()) {
                    ++_fetchCounter;
                }
_fetchCounterは10件カウントアップされているようですが
ここの_dataReaderは実データ取得時のものとは違うものですか?




/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_

        public FetchNarrowingResultSetWrapper(System.Data.IDataReader dataReader, FetchNarrowingBean fetchNarrowingBean
                                                 , bool offsetByCursorForcedly, bool limitByCursorForcedly) {
            _dataReader = dataReader;
            _fetchNarrowingBean = fetchNarrowingBean;
            _offsetByCursorForcedly = offsetByCursorForcedly;
            _limitByCursorForcedly = limitByCursorForcedly;

            skip();
        }
                
        private void skip() {
            if (!IsAvailableSkipRecord()) {
                return;
            }
            int skipStartIndex = GetFetchNarrowingSkipStartIndex();
            if (IsCursorUsed()) {
                //if (0 == skipStartIndex) {
                //    _dataReader.beforeFirst();
                //} else {
                //    _dataReader.absolute(skipStartIndex);
                //}
                //_fetchCounter = _dataReader.getRow();
                throw new NotSupportedException("Cursor is unsupported!!!");
            } else {
                while (_fetchCounter < skipStartIndex && _dataReader.Read()) {
                    ++_fetchCounter;
                }
            }
        }
        



2019年10月28日月曜日 15時35分56秒 UTC+9 jflute:

kubo

unread,
Oct 28, 2019, 6:12:41 AM10/28/19
to DBFluteユーザの集い
jfluteです

> ここの_dataReaderは実データ取得時のものとは違うものですか?

その実体クラスがなにかも追ってみてください。

恐らく、FetchNarrowingResultSetWrapperの_dataReaderが、
ADO.NETの生の DataReader で、
それが FetchNarrowingResultSetWrapper にラップされて、
DataReaderHandlerでwhileループでRead()されてるんじゃないかと。

なので、本当にデータが取得されているかどうかは、
ADO.NETの生の _dataReader を見るとわかります。

もし、取得されていなければ、さらに「うーむー」という感じですが、
もし、取得されていれば、FetchNarrowingResultSetWrapperの処理が、
何かしらうまく連携していないんじゃないかと推測できます。

FetchNarrowingResultSetWrapperの中の処理を詳しく追ってみると良いと思います。

kubo

unread,
Oct 28, 2019, 11:00:58 AM10/28/19
to DBFluteユーザの集い
jfluteです

加えて、FetchNarrowingResultSetFactory の CreateDataReader() の中の
挙動を追ってみると良いです。


外だしSQLの ManualPaging のときって、
IsUseFetchNarrowingResultSetWrapper() は true になるべきじゃないような気がするので、
そもそも FetchNarrowingResultSetWrapper が利用されているのがおかしいかも!?

志水正幸

unread,
Oct 29, 2019, 8:34:46 PM10/29/19
to DBFluteユーザの集い
志水です。

おはようございます。。

ORACLE版の方で正解どうなんだと思って
追ってみましたが、ORACLE版でも
デバッグだと、たまに挙動がおかしくなり
変数値参照していると急に落ちゃったり
2頁目なのにそのままスルーして最終頁表示されたりします。
といっても、データはちゃんと正常に画面に表示されているんですけどね。。
デバッグだとおかしくなっちゃうんですかね~??


>加えて、FetchNarrowingResultSetFactory の CreateDataReader() の中の 
>挙動を追ってみると良いです。 
正直、細かい挙動の正解がわからないので
追うのが厳しくなってきた感がありますorz
とりあえず、Debug.WriteLineで
CreateDataReader内のフラグ値を追ってみました。



1頁目の初期画面表示
★★★ CreateDataReader Start!! ★★★
fnbean.SafetyMaxResultSize :0
fnbean.IsFetchNarrowingEffective :False
fnbean.IsFetchNarrowingLoopCountEffective :False
IsUseFetchNarrowingResultSetWrapper :false
★★★ CreateDataReader Retire End!! Because IsUseFetchNarrowingResultSetWrapper :false ★★★
★★★ CreateDataReader Start!! ★★★
fnbean.SafetyMaxResultSize :0
fnbean.IsFetchNarrowingEffective :True
fnbean.IsFetchNarrowingLoopCountEffective :False
IsUseFetchNarrowingResultSetWrapper :true
OutsideSqlContext.IsExistOutsideSqlContextOnThread() :true
★★★ CreateDataReader End!! ★★★



2頁目の画面表示
★★★ CreateDataReader Start!! ★★★
fnbean.SafetyMaxResultSize :0
fnbean.IsFetchNarrowingEffective :False
fnbean.IsFetchNarrowingLoopCountEffective :False
IsUseFetchNarrowingResultSetWrapper :false
★★★ CreateDataReader Retire End!! Because IsUseFetchNarrowingResultSetWrapper :false ★★★
★★★ CreateDataReader Start!! ★★★
fnbean.SafetyMaxResultSize :0
fnbean.IsFetchNarrowingEffective :True
fnbean.IsFetchNarrowingLoopCountEffective :False
IsUseFetchNarrowingResultSetWrapper :true
OutsideSqlContext.IsExistOutsideSqlContextOnThread() :true
★★★ CreateDataReader End!! ★★★
★★★ CreateDataReader Start!! ★★★
fnbean.SafetyMaxResultSize :0
fnbean.IsFetchNarrowingEffective :False
fnbean.IsFetchNarrowingLoopCountEffective :False
IsUseFetchNarrowingResultSetWrapper :false
★★★ CreateDataReader Retire End!! Because IsUseFetchNarrowingResultSetWrapper :false ★★★
★★★ CreateDataReader Start!! ★★★
fnbean.SafetyMaxResultSize :0
fnbean.IsFetchNarrowingEffective :True
fnbean.IsFetchNarrowingLoopCountEffective :False
IsUseFetchNarrowingResultSetWrapper :true
OutsideSqlContext.IsExistOutsideSqlContextOnThread() :true
★★★ CreateDataReader End!! ★★★

kubo

unread,
Oct 29, 2019, 9:00:28 PM10/29/19
to DBFluteユーザの集い
jfluteです

おお、ありがとうございます。
IsUseFetchNarrowingResultSetWrapper が true になっている箇所がありますね。

Java版で検証してみたのですが、ズバリ!
ManualPagingのときは、IsUseFetchNarrowingResultSetWrapper が true にはならないのが正解です。
つまり、FetchNarrowingResultSetWrapper が利用されないのが正解です。

FetchNarrowingResultSetWrapper は AutoPaging 用のクラスで、
カーソルスキップをするためのものです。

カーソルスキップがわからない場合は、以下ページを読んでみてください。

// そもそもページング検索とは?
http://dbflute.seasar.org/ja/manual/topic/programming/dbaccess/pagingselect.html

ManualPagingなのにFetchNarrowingResultSetWrapperが動くということは、
SQLスキップしながらもカーソルスキップされちゃうということで、
明らかに想定しない挙動になると思います。


Oracleでも挙動がちょっとおかしいということは、
Oracleのときでも IsUseFetchNarrowingResultSetWrapper が true になってることが
あるかもしれませんね。

原因の選択肢としては、フレームワークの中で判定が間違っているというのと、
アプリケーションの中でManualPagingなのにAutoPagingしてしまっている、
というのが考えられます。

ひとまず、アプリの中でManualPagingとして呼び出していますでしょうか?
確認してみてください。

志水正幸

unread,
Oct 29, 2019, 9:43:38 PM10/29/19
to DBFluteユーザの集い
志水です。

ManualPagingを利用しています。
使い方合ってるとおもうんですが・・・

        /// <summary>
        /// ページ単位データ取得
        /// </summary>
        /// <param name="cnd"></param>
        /// <param name="pageNumber"></param>
        /// <returns></returns>
        public Object GetManyualPagingSelectList(DenpyoCondition cnd, int pageNumber)
        {
            UkagaiKensakuPmb pmb = new UkagaiKensakuPmb();
            
            pmb.Kaisya_cd = cnd.Kaisya_Cd;
            pmb.Kaikei_cd = cnd.Kaikei_Cd;
            pmb.Nendo = cnd.Nendo;
            pmb.Denpyo_no_ST = cnd.Denpyo_No_ST;
            pmb.Denpyo_no_ED = cnd.Denpyo_No_ED;
            pmb.Denpyo_kbn = cnd.Denpyo_Kbn;
            pmb.SetKian_ymd_ST_FromDate(cnd.Kian_YMD_ST);
            pmb.SetKian_ymd_ED_ToDate(cnd.Kian_YMD_ED);
            pmb.Kian_user = cnd.Kian_User;
            pmb.Kessai = cnd.Kessai;
            
            pmb.Paging(cnd.DspPageSize, pageNumber);

            string path = TUkagaiBhv.PATH_selectUkagaiKensaku;

            return tUkagaiBhv.OutsideSql().ManualPaging().SelectPage<UkagaiKensaku>(path, pmb);
        }



2019年10月30日水曜日 10時00分28秒 UTC+9 jflute:

kubo

unread,
Oct 29, 2019, 10:01:53 PM10/29/19
to DBFluteユーザの集い
jfluteです

> return tUkagaiBhv.OutsideSql().ManualPaging().SelectPage<UkagaiKensaku>(path, pmb);

ふむぅ、確かにManualPagingしていますねぇ..

ManualPagingのExampleは、dbflute.net-quill-example にあります。
こちらで検証してみるのも良いかも知れません。(何が違うんだろう...)
https://github.com/seasarorg/dbflute.net-example/blob/master/dbflute.net-quill-example/source/DfExampleTest/DBFlute/HowTo/Jp/BehaviorMiddleTest.cs

ひとまず、IsUseFetchNarrowingResultSetWrapperがtrueになる原因を追ってみてください。
ちょと出かけるので夕方また復活します。

kubo

unread,
Oct 29, 2019, 10:06:58 PM10/29/19
to DBFluteユーザの集い
jfluteです

ちなみに、
FetchNarrowingResultSetWrapper でラップされている、
(恐らくADO.NET生の) dataReader であれば、
2ページ目であってもデータが取得されていること、
って確認されていましたっけ?

(まず先にそこを見られたほうが良いです。
いま検証している要因が確実に合っているかどうか)

もし、生の dataReader がデータ取得できているのであれば、
FetchNarrowingResultSetWrapper が利用されていることが原因だと確実に言えそうです。

kubo

unread,
Oct 30, 2019, 2:56:53 AM10/30/19
to DBFluteユーザの集い
jfluteです

dbflute.net-quill-example での検証はそれはそれでやってみた方が良いとして...
その上で...

> ★★★ CreateDataReader Start!! ★★★
> fnbean.SafetyMaxResultSize :0
> fnbean.IsFetchNarrowingEffective :True
> fnbean.IsFetchNarrowingLoopCountEffective :False
> IsUseFetchNarrowingResultSetWrapper :true
> OutsideSqlContext.IsExistOutsideSqlContextOnThread() :true
> ★★★ CreateDataReader End!! ★★★

志水さん、2ページ目の検索時の、
IsUseFetchNarrowingResultSetWrapper() が true になってるときの、
そのメソッド内での分岐を細かく見て頂けないでしょうか?
どの分岐の何がtrueで、return true になっているのか?を踏まえて、
こちらでコード分析してみたいなと。

(ManualPagingの場合は、これがreturn trueになってはいけないはずなので...)

protected bool IsUseFetchNarrowingResultSetWrapper() {
FetchNarrowingBean fnbean =
FetchNarrowingBeanContext.GetFetchNarrowingBeanOnThread();

// for safety result
if (fnbean != null && fnbean.SafetyMaxResultSize > 0) {
return true;
}

// for unsupported paging (ConditionBean)
if (fnbean != null && fnbean.IsFetchNarrowingEffective) {
// for unsupported paging (Database)
if (fnbean.IsFetchNarrowingSkipStartIndexEffective ||
fnbean.IsFetchNarrowingLoopCountEffective) {
return true;
}

// for auto paging (OutsideSql)
if (OutsideSqlContext.IsExistOutsideSqlContextOnThread()) {
OutsideSqlContext outsideSqlContext =
OutsideSqlContext.GetOutsideSqlContextOnThread();
if (outsideSqlContext.IsOffsetByCursorForcedly ||
outsideSqlContext.IsLimitByCursorForcedly) {
return true;
}
}
}
return false;
}

志水正幸

unread,
Oct 30, 2019, 5:18:24 AM10/30/19
to DBFluteユーザの集い
志水です。


とりあえず簡単なほうから
protected bool IsUseFetchNarrowingResultSetWrapper()の
動きをとってみました。

/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_
<初期画面表示(1頁目)>
★★★ IsUseFetchNarrowingResultSetWrapper Start!!★★★
fnbean.SafetyMaxResultSize:0
fnbean.IsFetchNarrowingEffective:False
★★★ IsUseFetchNarrowingResultSetWrapper End!!★★★
★★★ IsUseFetchNarrowingResultSetWrapper Start!!★★★
fnbean.SafetyMaxResultSize:0
fnbean.IsFetchNarrowingEffective:True
fnbean.IsFetchNarrowingSkipStartIndexEffective:True
fnbean.IsFetchNarrowingLoopCountEffective:False
★★★ IsUseFetchNarrowingResultSetWrapper Retire End!! Because if (fnbean.IsFetchNarrowingSkipStartIndexEffective || fnbean.IsFetchNarrowingLoopCountEffective) => true ★★★


<2頁目ボタンを押下>

★★★ IsUseFetchNarrowingResultSetWrapper Start!!★★★
fnbean.SafetyMaxResultSize:0
fnbean.IsFetchNarrowingEffective:False
★★★ IsUseFetchNarrowingResultSetWrapper End!!★★★



★★★ IsUseFetchNarrowingResultSetWrapper Start!!★★★
fnbean.SafetyMaxResultSize:0
fnbean.IsFetchNarrowingEffective:True
fnbean.IsFetchNarrowingSkipStartIndexEffective:True
fnbean.IsFetchNarrowingLoopCountEffective:False
★★★ IsUseFetchNarrowingResultSetWrapper Retire End!! Because if (fnbean.IsFetchNarrowingSkipStartIndexEffective || fnbean.IsFetchNarrowingLoopCountEffective) => true ★★★



★★★ IsUseFetchNarrowingResultSetWrapper Start!!★★★
fnbean.SafetyMaxResultSize:0
fnbean.IsFetchNarrowingEffective:False
★★★ IsUseFetchNarrowingResultSetWrapper End!!★★★


★★★ IsUseFetchNarrowingResultSetWrapper Start!!★★★
fnbean.SafetyMaxResultSize:0
fnbean.IsFetchNarrowingEffective:True
fnbean.IsFetchNarrowingSkipStartIndexEffective:True
fnbean.IsFetchNarrowingLoopCountEffective:False
★★★ IsUseFetchNarrowingResultSetWrapper Retire End!! Because if (fnbean.IsFetchNarrowingSkipStartIndexEffective || fnbean.IsFetchNarrowingLoopCountEffective) => true ★★★



以上、宜しくお願い致します。


2019年10月30日水曜日 15時56分53秒 UTC+9 jflute:

kubo

unread,
Oct 30, 2019, 5:29:02 AM10/30/19
to DBFluteユーザの集い
jfluteです

ありがとうございます。

> ★★★ IsUseFetchNarrowingResultSetWrapper Start!!★★★
> fnbean.SafetyMaxResultSize:0
> fnbean.IsFetchNarrowingEffective:True
> fnbean.IsFetchNarrowingSkipStartIndexEffective:True
> fnbean.IsFetchNarrowingLoopCountEffective:False

fnbean.IsFetchNarrowingSkipStartIndexEffective:True

が、おかしいですね。

いま推測ですが、SQLServerって、もともと row_number は存在せず、
select top 10 ... 構文でしかページングができませんでした。

なので、いまもConditionBeanでページングすると、
select top 10 ... 構文になると思います。(DBFlute.NETにおいて)
その場合、offsetの処理だけカーソルスキップになるので、
ConditionBean の場合は SkipStartIndex が true になるのですね。

でも、外だしSQLのManualPagingの場合はそこに関係なく、
SkipStartIndex は False にならないきゃいけないのですが、
上記の現象からすると何かしら影響が出ちゃってるんじゃないかと、
いまちょっと仮説です。

MySQLのExampleプロジェクトは最近は動かしてないですが、
以前は確実に動いていたと思うので、MySQLなら最初からoffset/limitが
サポートされていたのでそこは影響しなかったんだろうと。
Oracleもrownumがあるので影響しなかったんだろうと。
(Oracleでも想定外の挙動するかも!? ってのはまた置いておいて)
というのがつじつま合うなぁと。

こちらでコード分析もしますが、

IsFetchNarrowingSkipStartIndexEffective

が、なぜ true になっているのか、追ってみて頂けると嬉しいです。

kubo

unread,
Oct 30, 2019, 5:34:21 AM10/30/19
to DBFluteユーザの集い
jfluteです

続きです。

そのときの、fnbean の実体クラスって何か見てみてください。
(ログで、GetClass() でしたっけ!? を出して頂ければと)

FetchNarrowingBean fnbean =
FetchNarrowingBeanContext.GetFetchNarrowingBeanOnThread();
...
// for unsupported paging (ConditionBean)
if (fnbean != null && fnbean.IsFetchNarrowingEffective) {
// for unsupported paging (Database)
if (fnbean.IsFetchNarrowingSkipStartIndexEffective ||
fnbean.IsFetchNarrowingLoopCountEffective) {
return true;
}

// for auto paging (OutsideSql)
if (OutsideSqlContext.IsExistOutsideSqlContextOnThread()) {
OutsideSqlContext outsideSqlContext =
OutsideSqlContext.GetOutsideSqlContextOnThread();
if (outsideSqlContext.IsOffsetByCursorForcedly ||
outsideSqlContext.IsLimitByCursorForcedly) {
return true;
}
}
}

仮に AutoPaging で指定したときでも、評価されるのは、
OutsideSqlContext.IsExistOutsideSqlContextOnThread() の方の分岐なんですよね。
外だしSQLで、IsFetchNarrowingSkipStartIndexEffective の方の分岐が評価されるのが、
ちょっとおかしいなと。

FetchNarrowingBeanContext に ConditionBean が入っちゃってたりしないかなぁとか...

志水正幸

unread,
Oct 30, 2019, 6:22:53 AM10/30/19
to DBFluteユーザの集い
志水です。

>そのときの、fnbean の実体クラスって何か見てみてください。 
Pmbですね。。


"UkagaiKensakuPmb:{2391, 01, 2017, , , , , 2017/04/01 0:00:00, 2018/04/01 0:00:00, 1}"
"UkagaiKensakuPmb:{2391, 01, 2017, , , , , 2017/04/01 0:00:00, 2018/04/01 0:00:00, 1}"

"UkagaiKensakuPmb:{2391, 01, 2017, , , , , 2017/04/01 0:00:00, 2018/04/01 0:00:00, 1}"
"UkagaiKensakuPmb:{2391, 01, 2017, , , , , 2017/04/01 0:00:00, 2018/04/01 0:00:00, 1}"

"UkagaiKensakuPmb:{2391, 01, 2017, , , , , 2017/04/01 0:00:00, 2018/04/01 0:00:00, 1}"
"UkagaiKensakuPmb:{2391, 01, 2017, , , , , 2017/04/01 0:00:00, 2018/04/01 0:00:00, 1}"

kubo

unread,
Oct 30, 2019, 6:52:08 AM10/30/19
to DBFluteユーザの集い
jfluteです

ありがとうございます。
ということは、そのスーパークラスの SimplePagingBean ですね。

コードも読んで原因わかりました。


もともと、SQLServer は、row_number() がなく、
top構文 (limit) のみでのページングしかできませんでした。

ゆえに、ConditionBeanではoffsetでカーソルスキップを利用し、
外だしSQLでも AutoPaging だろうが ManualPaging だろうが、
offset 部分に関してはカーソルスキップが必要でした。

そう、ManualPagingでもカーソルスキップが必要なので、
SQLServerの場合は、ManualPagingでもカーソルスキップになるようにしていました。
実はそれが今回の現象です。

SQLとしては、top構文を使ったページングを行い、
ManualPaging を選ぶと、offsetはカーソルスキップで実現、limitはtop構文で実現、
というようになります。

外だしSQLなのに FetchNarrowingResultSetWrapper が使われるのはおかしいな、
というところでしたが、DBFlute.NETのSQLServer対応においてはそれが正しい挙動でした。


ただ、SQLServerのバージョンが上がり、row_number()関数が使えるようになりました。
で、Java版のDBFluteにおいては、外だしSQLのManualPagingを書くときはtop構文は使わず、
row_number()関数を使ってやりましょう、ということでフレームワークもドキュメントも
修正されています。(ただ、ConditionBeanは実装コストの都合でtop構文のままです)

ですが、DBFlute.NETの方は、上記のtop構文で外だしSQLのManualPagingを行うことが前提で、
今も実装されている状態です。.NETの方は、SQLServer2000が根強く利用され続けていたので、
そのままtop構文メインでの方式を維持していたというところです。


その状態で、今回は row_number() を使って外だしSQLを書かれているので、
そこでギャップが生まれてしまいました。
row_number() でも10件スキップしているのに、カーソルスキップでも10件スキップするので、
10件目から20件目のデータがまるごとカーソルスキップで無くなってしまうと。
(なので、MySQLやOracleでは発生しない現象です)


そういうことで、回避策としては...

A. top構文で書く
 - カーソルスキップに頼ることになりますが、パフォーマンス激劣化するわけじゃない
 - フレームワークをいじらずに回避できる (いまのDBFlute.NETが想定している実装に)

B. SimplePagingBean.vmnet をいじる
 - Java版に少しだけ近い実装をして、SQLServerでもカーソルスキップしないManualPagingに
 - フレームワークをいじることになる (パッチを当てるような感じ)


108 public bool IsFetchNarrowingEffective { get { return
this.SqlClause.isFetchNarrowingEffective(); } }
109 public void IgnoreFetchNarrowing() { _fetchNarrowing = false; }
110 public void RestoreIgnoredFetchNarrowing() {
_fetchNarrowing = true; }

 ↓↓↓ (108行目に_fetchNarrowingの判定をand条件で追加)

108 public bool IsFetchNarrowingEffective { get { return
_fetchNarrowing && this.SqlClause.isFetchNarrowingEffective(); } }
109 public void IgnoreFetchNarrowing() { _fetchNarrowing = false; }
110 public void RestoreIgnoredFetchNarrowing() {
_fetchNarrowing = true; }

Java版のSimplePagingBean.javaがそのようになっています。
https://github.com/dbflute/dbflute-core/blob/master/dbflute-runtime/src/main/java/org/dbflute/outsidesql/paging/SimplePagingBean.java#L291

別の箇所で、ManualPagingのときはこれが false になるようにセットされるので、
IsFetchNarrowingEffective が固定的に false になります。

すると、FetchNarrowingResultSetFactory の IsUseFetchNarrowingResultSetWrapper() は、
IsFetchNarrowingEffective() を使った分岐で false になるので、Wrapper は利用されません。

上記の修正をしても、ConditionBeanはSimplePagingBeanとは何も関わらないので影響ないはずで、
AutoPagingのときは、_fetchNarrowing が false にならないので、大丈夫なはずです。
(はず、というのは一度ちゃんと確認してみないと、という感じです)


色々と検証ありがとうございました。
なかなかDBFlute.NETのジレンマな部分で申し訳ないですが、
ひとまず、このような状況です。

志水正幸

unread,
Oct 30, 2019, 7:29:22 AM10/30/19
to DBFluteユーザの集い
志水です。

おお~。。
ありがとうございます。

ちょっと今のSQLをそのまま使いたいなぁと
思っているので、とりあえず案Bを試してみたいと思います。
動作確認して、問題有無にかかわらず
また報告を上げたいと思います。
色々とありがとうございました。




2019年10月30日水曜日 19時52分08秒 UTC+9 jflute:

志水正幸

unread,
Oct 30, 2019, 9:47:48 PM10/30/19
to DBFluteユーザの集い
志水です。

>B. SimplePagingBean.vmnet をいじる
ありがとうございました。
指示通り、修正し実行したところ
ページング処理の問題が解消されていました。
見たところ他の箇所への影響などはなさそうですが
これから本格的に修正に入っていくので
テストしていく中で挙動に関しては
注意してみておきたいと思います。


以上、宜しくお願い致します。

kubo

unread,
Oct 31, 2019, 12:24:46 AM10/31/19
to DBFluteユーザの集い
jfluteです

おお、良かったです。
他のユーザーにとっても貴重なやり取りとなったと思います。
改めて、調査・検証ありがとうございます。

志水正幸

unread,
Oct 31, 2019, 9:33:11 PM10/31/19
to DBFluteユーザの集い
志水です。

「SimplePagingBean.vmnet」を添付しておきます。




2019年10月31日木曜日 13時24分46秒 UTC+9 jflute:
SimplePagingBean.vmnet

志水正幸

unread,
Oct 31, 2019, 10:22:55 PM10/31/19
to DBFluteユーザの集い

バージョン書き忘れていました。
「dbflute-0.8.9.59」です




2019年11月1日金曜日 10時33分11秒 UTC+9 志水正幸:
Reply all
Reply to author
Forward
0 new messages