demasi@paisano.UUCP (Michael C. De Masi) (07/17/87)
Hello again people, If you remember my origional posting, I was the guy who was having so much trouble with Informix cursors. The two problems I was having involved: 1) The fact that they're seemed to be no way to back up a cursor in an active list after a fetch. 2) The fact that non- readonly activity seemed to break every cursor declared in the program at any given time. I actually got quite a few kind responses from the net on the first point, but general confusion on the second. For the answer to the second point, I had to consult with tech support at Informix. In summary: 1) No, there is no real way to back up a cursor in an active list. The command 'fetch' only works in one direction, and there is no equivalent of an 'unfetch'. I did, however, receive several strategies for getting around this problem, mostly involving creating temporary tables and/or files with the data or rowids from the given database tables. This is pretty much what I was already doing, and was looking to avoid. One fellow even suggested declaring two cursors, one going in ascending and the other in descending order, and querying and redeclaring them as they approached and overlapped each other. Also, from what I could glean, these problems are solved in 4GL with what they call a 'scroll cursor' with which one can issue commands like 'scroll up' 'scroll back' etc. 2) As to the second point, I seemed to get a great deal of confusion from the net regarding my statement of the problem. Put as simply as possible, it seemed that non-readonly activity (ie updates, deletes and insertions) seemed to make any currently declared & opened cursor in the program inoperative. The sqlca.sqlcode returned from the next fetch was -259 'cursor not open'. Nobody on the net could place the problem, so again I consulted with Informix. The problem was not with the update activity per se, but with the structure placed around it. To insure data integrity, I had put all update statements within the context of a 'begin work ... commit work else rollback work' structure. IMPORTANT: Both 'commit work' and 'rollback work' have the side effect of closing every active cursor in the whole damn program! (FLAME ON!!!) Now this is not limited to cursors acted upon within the structure, or indeed to cursors declared for update, but all of them. The nice person I spoke to at Informix assured me that this was indeed a documented feature of both 'commit work' and 'rollback work'. Documented or not, I find this a rather bizarre concept. I thought the purpose of this structure was to insure transaction integrity, not to do cleanup work of record locks, etc. In my humble opinion, this is the kind of thing best left to the programmer or to the database administrator itself and should not be hidden within the context of a seemingly non-related functionality. (FLAME OFF!!!) Lord, do I feel better. Anyway, to be fair to Informix, with the exception of the above probs (which I'll admit I probably would have avoided if I'd read the doc a bit more carefully) ESQL/C is a good tool, and has served me well. Till next time, -- Michael C. De Masi - AT&T Communications (For whom I work and not speak) 3702 Pender Drive, Fairfax, Virginia 22030 Phone: 703-246-9555 UUCP: seismo!decuac!grebyn!paisano!demasi "Life. Don't tell me about life." - Marvin, the paranoid android
mjr@well.UUCP (Matthew Rapaport) (07/20/87)
In article <229@paisano.UUCP> demasi@paisano.UUCP (Michael C. De Masi) writes: >Anyway, to be fair to Informix, with the exception of the above probs >(which I'll admit I probably would have avoided if I'd read the doc a >bit more carefully) ESQL/C is a good tool, and has served me well. > This also goes to prove a point I've been making in a column... That even state-of-the-art non-procedural 4GL's are NOT so simple that any end user can sit down and write complex retrieval and update operations. This work is best left to specialists, no matter what the level of language interface... mjr@well (ptsfa!well!mjr)
jeff@rtech.UUCP (Jeff Lichtman) (07/21/87)
From article <229@paisano.UUCP>, by demasi@paisano.UUCP (Michael C. De Masi): > To insure data integrity, I had put all update statements within the > context of a 'begin work ... commit work else rollback work' structure. > > IMPORTANT: Both 'commit work' and 'rollback work' have the side effect > of closing every active cursor in the whole damn program! > > (FLAME ON!!!) > Now this is not limited to cursors acted upon within the structure, or > indeed to cursors declared for update, but all of them. The nice person > I spoke to at Informix assured me that this was indeed a documented > feature of both 'commit work' and 'rollback work'. Documented or not, > I find this a rather bizarre concept. I thought the purpose of this > structure was to insure transaction integrity, not to do cleanup work > of record locks, etc. In my humble opinion, this is the kind of thing > best left to the programmer or to the database administrator itself and > should not be hidden within the context of a seemingly non-related > functionality. > (FLAME OFF!!!) Lord, do I feel better. > -- > Michael C. De Masi - AT&T Communications (For whom I work and not speak) Closing all cursors when committing or rolling back a transaction makes sense, and conforms to the ANSI SQL standard. It is easier to explain why "rollback work" should close all open cursors, so let me start with that. The "rollback work" statement is supposed to reset the database to the state it was in before the transaction began. It would be nearly impossible, in the general case, to position cursors properly after a "rollback work"; for example, suppose you had deleted a bunch of rows from a table, positioned a cursor just before these deleted rows, and then did a "rollback work". The deleted rows have to come back, so should the next row you fetch be the first deleted row or the one after the deleted row? Now, about releasing locks when committing or rolling back a transaction: The purpose of a transaction is to provide atomicity. That is, transactions assure that you don't see anyone else's updates before they're complete, and that no one sees your updates before they're complete. Transactions are also the basic unit of recovery (in case of a system crash, for example, all incomplete transactions will be backed out during recovery). Locking is only a technique for providing atomicity. Database systems release locks at commit or rollback time because they are no longer needed: the transaction has either been entirely committed or entirely backed out, so the locks can be released safely without jeopardizing consistency. To hold the locks longer than this wouldn't hurt consistency, but it would kill concurrency. It would be possible to build a system that would allow the programmer to control when the locks were released, but that would be putting the burden of maintaining database consistency on the programmer; that's a service that the DBMS should provide, and is one of the things that distinguishes a DBMS from a file system. Now that we've established why the DBMS releases locks at commit or rollback time, let's consider why cursors must be closed at commit time. Suppose you are in the middle of a transaction, and you have a cursor positioned in the middle of a table. Now you do a "commit work". Suppose we want the cursor to remain open; the DBMS would still have to release its locks, which would mean that the cursor couldn't maintain whatever locks it had on its current position. Suppose, before you tried to fetch the next row from the cursor, that someone destroyed the underlying table, or deleted the row that the cursor was going to advance to, or added a row in front of the one it was to advance to. What should happen? Not only would it be extremely difficult to implement the DBMS to account for all possible cases of trying to use a cursor that has had the locks swept out from under it, it would be difficult to define what should happen in all cases (and many definitions would necessarily be arbitrary). It seems that Mr. De Masi got into this mess by trying to use "commit work" and "rollback work" to guarantee the success or failure of individual database statements. That is not a correct use of transactions. It appears to me that Mr. De Masi is looking for statement atomicity; that is, he wants every individual database statement to completely succeed or be backed out. This is something that every relational DBMS should provide for the user. I'm not familiar enough with Informix to say whether it does this, but it's a pretty good bet that it does. Therefore, there should be no need to test every update and either commit it or roll it back; the DBMS will leave in the effects of every successful statement, and back out the effects of every statement that couldn't complete due to some DBMS error. In SQL, the effects of all successful statements in the current transaction are committed at the time of a "commit work" statement, or backed out at the time of a "rollback work" statement. It's possible that I misinterpreted Mr. De Masi's statements, and what he's really looking for is a general-purpose way of getting rid of unwanted updates (e.g. a program makes a successful update, then later on in the transaction it decides it shouldn't have made the update and needs to get rid of it). Standard SQL doesn't provide any tools for doing this, and I'm not aware of any DBMS that makes this easy for you. If your tables all have unique primary keys (always a good idea), you could write your program so that it remembers the old row and the new key; that way, you could put back the old row if you wanted. -- Jeff Lichtman at rtech (Relational Technology, Inc.) "Saints should always be judged guilty until they are proved innocent..." {amdahl, sun}!rtech!jeff {ucbvax, decvax}!mtxinu!rtech!jeff
demasi@paisano.UUCP (Michael C. De Masi) (07/22/87)
In article <1076@rtech.UUCP>, jeff@rtech.UUCP (Jeff Lichtman) writes: > From article <229@paisano.UUCP>, by demasi@paisano.UUCP (Michael C. De Masi): > > To insure data integrity, I had put all update statements within the > > context of a 'begin work ... commit work else rollback work' structure. > > > > IMPORTANT: Both 'commit work' and 'rollback work' have the side effect > > of closing every active cursor in the whole damn program! > > > > (FLAME ON!!!) ..... I complain for a while about the above activity ..... > > (FLAME OFF!!!) Lord, do I feel better. > > -- > > Michael C. De Masi - AT&T Communications (For whom I work and not speak) > > Closing all cursors when committing or rolling back a transaction makes > sense, and conforms to the ANSI SQL standard. > ..... Mr Lichtman explains his above statement nicely ..... > > Now that we've established why the DBMS releases locks at commit or > rollback time, let's consider why cursors must be closed at commit > time. Suppose you are in the middle of a transaction, and you have > a cursor positioned in the middle of a table. Now you do a "commit work". > Suppose we want the cursor to remain open; the DBMS would still have > to release its locks, which would mean that the cursor couldn't maintain > whatever locks it had on its current position. Suppose, before > you tried to fetch the next row from the cursor, that someone destroyed > the underlying table, or deleted the row that the cursor was going to > advance to, or added a row in front of the one it was to advance to. > What should happen? Not only would it be extremely difficult to > implement the DBMS to account for all possible cases of trying to > use a cursor that has had the locks swept out from under it, it would > be difficult to define what should happen in all cases (and many > definitions would necessarily be arbitrary). > Now granted, I probably don't have the level of understanding about database theory that you do, but what about purely readonly queries that do not cause any locking? Granted, commiting an update does change the nature of a given table, but can't that happen anyway? That is, if I do a readonly query of a group of database records, and one gets changed by another process or user before I advance to that record, won't I get incorrect or no longer existing data, or is there some functionality that takes care of this of which I'm not aware? If so, couldn't this same fuctioanlity be used to keep readonly cursors open under the circumstances I've described? > It seems that Mr. De Masi got into this mess by trying to use "commit > work" and "rollback work" to guarantee the success or failure of > individual database statements. That is not a correct use of transactions. > It appears to me that Mr. De Masi is looking for statement atomicity; that is, > he wants every individual database statement to completely succeed or > be backed out. This is something that every relational DBMS should provide for > the user. I'm not familiar enough with Informix to say whether it does > this, but it's a pretty good bet that it does. Therefore, there should > be no need to test every update and either commit it or roll it back; No, not unless you consider that one database 'transaction' from the viewpoint of the user, may actually consist of any number of individual database statements, any one of which is subject to failure. This is especially true in the case of relational databases, where the designer is encouraged to divide logical records over any number of seperate and distinct tables. The actual 'update' of which I wrote in actuality consists of at least two 'update' statements, a possible deletion and a variable number of possible insertions which in scope spread themselves over at least six seperate tables. Every one of these statements must be and is checked for error, and although you are correct, Informix does indeed automatically rollback statements that cause database errors, the 'begin/commit/rollback work' structure is critical to insure that the _entire_ transaction can be rolled back, to make the fuctionality of the user's 'update' command truly atomic. > It's possible that I misinterpreted Mr. De Masi's statements, Yup! But hey, what the hell, like you said: > "Saints should always be judged guilty until they are proved innocent..." ;-) Anyway, Jeff, thanks a lot for the input. I really do appreciate any help/explanations/arguments on this or any subject regarding making my job easier! Till the next disaster, -- Michael C. De Masi - AT&T Communications (For whom I work and not speak) 3702 Pender Drive, Fairfax, Virginia 22030 Phone: 703-246-9555 UUCP: seismo!decuac!grebyn!paisano!demasi "Life. Don't tell me about life." - Marvin, the paranoid android
jeff@rtech.UUCP (Jeff Lichtman) (07/25/87)
From article <230@paisano.UUCP>, by demasi@paisano.UUCP (Michael C. De Masi): > In article <1076@rtech.UUCP>, jeff@rtech.UUCP (Jeff Lichtman) writes: >> From article <229@paisano.UUCP>, by demasi@paisano.UUCP (Michael C. De Masi): >> >> Now that we've established why the DBMS releases locks at commit or >> rollback time, let's consider why cursors must be closed at commit >> time... > > Now granted, I probably don't have the level of understanding about > database theory that you do, but what about purely readonly queries that > do not cause any locking? Usually, a DBMS will get and hold shared locks (also called "read locks") until end transaction time. What most DBMS's guarantee by default is serializability, also called "level-three consistency". A set of concurrent transactions is serializable if there is some way of doing all of the transactions non-concurrently and getting the same result. That is, if you can untangle all of the interleaved operations in your concurrent transactions, and then can find some way of laying your transactions end-to-end in such a way that it would produce the same result, the transactions are called serializable. This can be a hard concept to absorb at first, so I'll put it a different way: the job of concurrency control in a DBMS is to ensure that users get a consistent view of the data. For a read-only transaction, this requires two things: that the transaction not be able to see other users' uncommitted updates, and that reading the same record more than once within the same transaction will yield the same values every time. This requires getting and holding read locks until end transaction time, even if it is a read-only transaction. Suppose, for instance, that you have database containing account information. There are two programs running: one is using a read-only transaction to add up all the accounts to make sure things balance. Another is running transactions to transfer funds between accounts. If the read-only transaction didn't get read locks, it would be possible for it to see some half-done updates from the transfer transactions, and the accounts wouldn't balance. (Note that there are other methods of concurrency control besides locking. I won't discuss any of the other methods here.) Now suppose that a similar situation exists, except the read-only transaction has two parts: first, it tallies all accounts with a positive balance, and then it tallies all accounts with a negative balance. Again, assume that there is another set of transactions going on at the same time that transfer funds between accounts. If the read-only transaction doesn't hold its locks until end transaction time, it's possible that an update to an account will cause it to be seen as a positive-balance account on the first pass, and a negative-balance account on the second pass. Thus, in order to guarantee the user a consistent view of the data, a DBMS must get and hold locks, even in a read-only transaction. This is true in the general case. However, if the DBMS knew enough about the user's application, it might be able to figure out when locks were no longer needed and release them earlier. For example, a transaction that does nothing but read from a single table, and never re-reads anything, doesn't need to hold its read locks until the end of the transaction; it only needs to hold its locks on each record, page, or whatever, until the application is done with the it. Please note, however, that a DBMS has no way of knowing whether this will be true, because it doesn't have the brains to study the application to see what it's doing. The programmer does have the brains, however. Some DBMS's allow the user to control locking in various ways; Ingres, for instance, allows the user to tell it not to use locks when reading. But all relational DBMS's that I know of use level-three consistency as the default. That is, some DBMS's allow the user to risk seeing inconsistent views of the data, but it must be done by the explicit action of the user. > Granted, commiting an update does change the > nature of a given table, but can't that happen anyway? That is, if I > do a readonly query of a group of database records, and one gets changed > by another process or user before I advance to that record, won't I get > incorrect or no longer existing data, or is there some functionality > that takes care of this of which I'm not aware? If so, couldn't this > same fuctioanlity be used to keep readonly cursors open under the > circumstances I've described? This could be a problem if read-only transactions didn't get and hold locks. But they do, and a consequence is that even read-only transactions won't see another transaction's incomplete updates. It is possible for one transaction to see another's *comitted* updates, but that's perfectly OK. >> It seems that Mr. De Masi got into this mess by trying to use "commit >> work" and "rollback work" to guarantee the success or failure of >> individual database statements... > > No, not unless you consider that one database 'transaction' from the > viewpoint of the user, may actually consist of any number of individual > database statements, any one of which is subject to failure... It sounded to me like the application was written something like this: database statement; if (success) commit work; else rollback work; database statement; if (success) commit work; else rollback work; database statement; if (success) commit work; else rollback work; ... commit work; When it should be written like this: database statement; if (failure) rollback work; database statement; if (failure) rollback work; database statement; if (failure) rollback work; ... commit work; I have left out some control structures, but you get the idea. "Success" and "failure" here don't necessarily refer to the database statements; they could be talking about other conditions that arise in the application. > Michael C. De Masi - AT&T Communications (For whom I work and not speak) > 3702 Pender Drive, Fairfax, Virginia 22030 Phone: 703-246-9555 > UUCP: seismo!decuac!grebyn!paisano!demasi > "Life. Don't tell me about life." - Marvin, the paranoid android -- Jeff Lichtman at rtech (Relational Technology, Inc.) "Saints should always be judged guilty until they are proved innocent..." {amdahl, sun}!rtech!jeff {ucbvax, decvax}!mtxinu!rtech!jeff
garyp@cognos.uucp (Gary Puckering) (07/30/87)
In article <1076@rtech.UUCP> jeff@rtech.UUCP (Jeff Lichtman) writes: >Now, about releasing locks when committing or rolling back a transaction: > ... >Database systems release locks at commit or rollback time because they >are no longer needed: the transaction has either been entirely committed >or entirely backed out, so the locks can be released safely without >jeopardizing consistency. To hold the locks longer than this wouldn't >hurt consistency, but it would kill concurrency. > ... >Suppose we want the cursor to remain open; the DBMS would still have >to release its locks, which would mean that the cursor couldn't maintain >whatever locks it had on its current position. Your statements seem inconsistent. Why would the DBMS *have* to release its locks? I thought you said that they were released because it was safe to do so, and desirable for good concurrency. You haven't given a reason why it is necessary. >Suppose, before >you tried to fetch the next row from the cursor, that someone destroyed >the underlying table, or deleted the row that the cursor was going to >advance to, or added a row in front of the one it was to advance to. For almost all the applications I can think of which need the cursor to survive a commit, it would be sufficient to end the first transaction and begin another immediately, retaining locks on all objects which the active cursors of the first transaction were addressing. Locks on other objects (i.e. those which the transaction did not have addressability to at commit time) could be released. This approach would provide another way of ending transactions beside the traditional COMMIT and ROLLBACK. A good name for it escapes me, but you are essentially ending a transaction, inheriting its cursor (and requisite locks), and beginning a new transaction. >What should happen? Not only would it be extremely difficult to >implement the DBMS to account for all possible cases of trying to >use a cursor that has had the locks swept out from under it, it would >be difficult to define what should happen in all cases (and many >definitions would necessarily be arbitrary). In the scheme I describe, the problem is avoided by not releasing locks on objects which the cursors are addressing. > >It seems that Mr. De Masi got into this mess by trying to use "commit >work" and "rollback work" to guarantee the success or failure of >individual database statements. That is not a correct use of transactions. Mr. De Masi is not the only person to get himself into these problems. Lots of other people have gone down the same garden path, led by tantalizing database systems that do so many things for you automatically that you can't get it to do what *you* want. Suppose I write a simple on-line transaction program which allows me to browse airline seat reservations given a partial name. Assume there are several qualifying records. When I find the one I'm looking for, I want to cancel the reservation. If all this is done on a single transaction, then every record I read will be locked. Suppose I decide not to cancel the reservation, and instead go to lunch. This leaves a bunch of records locked for a long period. The other clerks get very mad at me. To avoid this, I could write the application so that browsing is done on a versioned read (a read-only transaction). This would eliminate the locks. But now I have to do the update on another transaction (one that allows updating)! But how do I get addressability to the record I found during browsing? How do I know that it hasn't changed between the time I started the versioned read and the time I fetched it on the update transaction? Thanks for all the help, database system, but why couldn't you just preserve my cursor across a COMMIT! Actually, as I've mentioned in another article, I think there are lots of applications where degree 3 consistency is overkill and that the database system should allow you the option of degree 2 consistency (with a stable cursor). Both DB2 and SQL/DS provide this feature (although the DB2 implementation is rather flakey). -- Gary Puckering 3755 Riverside Dr. Cognos Incorporated Ottawa, Ontario {allegra,decvax,ihnp4,linus,pyramid} (613) 738-1440 CANADA K1G 3N3 !utzoo!dciem!nrcaer!cognos!garyp
speegle@im4u.UUCP (Greg Speegle) (08/03/87)
In article <1200@smokey.UUCP>, garyp@cognos.uucp (Gary Puckering) writes: > For almost all the applications I can think of which need the cursor > to survive a commit, it would be sufficient to end the first > transaction and begin another immediately, retaining locks on all > objects which the active cursors of the first transaction were addressing. > Locks on other objects (i.e. those which the transaction did not have > addressability to at commit time) could be released. > This approach would provide another way of ending transactions beside > the traditional COMMIT and ROLLBACK. A good name for it escapes me, > but you are essentially ending a transaction, inheriting its cursor > (and requisite locks), and beginning a new transaction. > In the scheme I describe, the problem is avoided by not releasing > locks on objects which the cursors are addressing. > Suppose I write a simple on-line transaction program which allows me > to browse airline seat reservations given a partial name. Assume > there are several qualifying records. When I find the one I'm looking > for, I want to cancel the reservation. If all this is done on a > single transaction, then every record I read will be locked. Suppose > I decide not to cancel the reservation, and instead go to lunch. This > leaves a bunch of records locked for a long period. The other clerks > get very mad at me. > To avoid this, I could write the application so that browsing is done > on a versioned read (a read-only transaction). This would eliminate > the locks. But now I have to do the update on another transaction > (one that allows updating)! But how do I get addressability to the > record I found during browsing? How do I know that it hasn't changed > between the time I started the versioned read and the time I fetched > it on the update transaction? Thanks for all the help, database > system, but why couldn't you just preserve my cursor across a COMMIT! The problem you propose here is called a long-duration transaction, which is a serious problem for design databases, like CAD systems. One solution, which solves many of the problems posted here, involves nested transactions. A nested transaction is a tree structured representation of transactions, where a leaf is a database operation (read/write) and a non-leaf node is a collection of nodes. At the level above the leaves, a node would be the same as a standard transaction, while the level above that would have a transaction as an operation. Thus, for your problem, one transaction would perform all the reads, and a second transaction would perform all of the writes, but both of these transactions would be subtransactions of the SAME TRANSACTION, thus locks could be preserved across the commit of the subtransaction. There are protocols which ensure serializability and other stuff as well. I have not heard of any production system which uses nested transactions, and if anyone out there does know of such a system, I would appreciate hearing about it, as nested transactions are part of my dissertation research. More information about nested transactions can be found in work done by J. Elliot B. Moss, currently of the University of Massachusetts, in his dissertation, now a book published by MIT Press called, "Nested Transactions". This work is primarily involved with distributed databases, but it clearly explains the concepts of nested transactions. Other references can be supplied on request. Apparently, the above is not sufficient text to get this article posted, so I'll ramble some more about nested transactions. The lock protocol proposed in Moss allowed lock sharing as well. A transaction could lock an object only if holders of incompatible locks are ancestors of the transaction needing the lock. Once a transaction finishes, it releases its lock to its parent. Thus, in the example problem presented here, the first subtransaction would attempt to get a read lock on the items needed, while the second transaction would aquire a write lock on the reservation which would need to be cancelled. the cancelled reservation would still be in the database because the parent of the "write" subtransaction would still hold the read locks. This is only a brief sketch of the concept, but I think it may prove very useful in more difficult database applications, such as CAD systems. Greg Speegle {ihnp4,siesmo,pyramid}!ut-sally!speegle speegle@sally.UTEXAS.EDU