Chapter 3 Using Multidimensional Storage (Global Variables) (4)

Regulatory affairs

InterSystems IRIS provides the basic operations needed to achieve complete transaction processing using global variables. InterSystems IRIS objects and SQL automatically take advantage of these features. You can use these operations if you write transactional data directly to global variables.

The transaction command is TSTART, which defines the start of a transaction; TCOMMIT, which commits the current transaction; And TROLLBACK, which aborts the current transaction and undoes any changes made to global variables since the beginning of the transaction.

For example, the following ObjectScript code defines the start of a transaction, sets some global variable nodes, and then commits or rolls back the transaction based on the value of OK:


/// w ##class(PHA.TEST.Global).GlobalTro(0)
ClassMethod GlobalTro(ok)
{

	TSTART

	Set ^Data(1) = "Apple1"
	Set ^Data(2) = "Berry1"

	If (ok) {
		TCOMMIT
	}
	Else {
	 	TROLLBACK
	}
	zw ^Data
	q ""
}

Copy the code

TSTART writes the transaction start flag to the InterSystems IRIS log file. This defines the initial bounds of the transaction. In the example above, if the variable OK is true(non-zero), the TCOMMIT command marks the successful completion of the transaction and writes the transaction completion flag to the log file. If OK is false(0), the TROLLBACK command undoes every set or kill operation that has taken place since the beginning of the transaction. In this case, ^Data(1) and ^Data(2) are restored to their original values.

Note that no data is written when the transaction completes successfully. This is because all changes to the database during a transaction are normally performed during the transaction. Only in the case of a rollback will the data in the database be affected. This means that the transaction in this example has limited isolation; That is, other processes can see the modified global value before the transaction commits. This is often referred to as an uncommitted read. Whether this is good or bad depends on the requirements of the application; In many cases, this is perfectly reasonable behavior. If an application requires a higher level of isolation, it can do so by using locks. This is described in the next section.

Lock and transaction

To create isolated transactions – that is, to prevent other processes from seeing modified data before committing the transaction – locks are used. In ObjectScript, the lock can be obtained and released directly using the lock command. The lock works as agreed; For a given data structure (such as for a persistent object), all code that requires a lock uses the same logical lock reference (that is, the lock command uses the same address).

In transactions, locks have a special behavior; Any locks acquired during a transaction are not released until the end of the transaction. To understand why this is the case, consider what a typical transaction does:

  1. useTSTARTStart the transaction.
  2. Gets locks on one or more nodes to be modified. This is often referred to as a “write” lock.
  3. Modify one or more nodes.
  4. Release locks (or locks). Because we are in a transaction, these locks will not actually be released at this point.
  5. useTCOMMITCommit the transaction. At this point, all the locks released in the previous step are actually released.

If another process wants to see the nodes involved in this transaction and does not want to see uncommitted changes, it simply tests the lock (called a “read” lock) before reading data from the node. Because the write lock remains until the end of the transaction, the reading process does not see the data until the transaction completes (commit or rollback).

Most database management systems use similar mechanisms to provide transaction isolation. InterSystems IRIS is unique in that it allows developers to use this mechanism. This makes it possible to create custom database structures for new application types while still supporting transactions. Of course, you can simply use InterSystems IRIS objects or SQL to manage data and have transactions managed automatically.

Nested calls to TSTART

InterSystems IRIS maintains a special system variable, $TLEVEL, that tracks the number of times the TSTART command is invoked. $TLEVEL starts at 0; Each time TSTART is called, the value of $TLEVEL increases by 1, and each time TCOMMIT is called, the value of $TLEVEL decreases by 1. If calling TCOMMIT results in $TLEVEL being set back to 0, the transaction ends (with COMMIT).

Invoking the TROLLBACK command always terminates the current transaction and sets $TLEVEL back to 0, regardless of the value of $TLEVEL.

This behavior enables applications to wrap transactions around code that itself contains transactions, such as object methods. For example, the %Save method provided by a persistent object always executes its operation as a transaction. By explicitly calling TSTART and TCOMMIT, you can create a larger transaction that contains several object save operations:

    TSTART
    Set sc = object1.%Save()
    If ($$$ISOK(sc)) {
        // The first save is valid. Perform the second save
        Set sc = object2.%Save()
    }
    
    If ($$$ISERR(sc)) {
        // One of them failed to save and is being rolled back
        TROLLBACK
    }
    Else {
        / / submit
        TCOMMIT
    }
Copy the code

Managing concurrency

The operation to set or retrieve a single global variable node is atomic; It ensures consistent success and consistent results. For operational or control transaction isolation on multiple nodes, InterSystems IRIS provides the ability to acquire and release locks.

Locks are managed by the IRIS lock manager. In ObjectScript, the lock can be obtained and released directly using the lock command. (InterSystems IRIS objects and SQL automatically acquire and release locks as needed).

Check for the latest global variable references

The latest global variable reference is recorded in ObjectScript$ZREFERENCESpecial variable.$ZREFERENCEContains the latest global references, including subscripts and extended global references if specified. Please note that,$ZREFERENCEIt does not indicate whether the global reference was successful or whether the specified global exists. InterSystems IRIS records only the most recently specified global references.

Naked global variable references

After a global reference with subscript, InterSystems IRIS sets the bare indicator to the global name and subscript level. Subsequent references to the same global variables and subscript levels can then be made using bare global references (omitting global names and higher-level subscripts). This simplifies double references to the same global variables at the same (or lower) subscript level.

Specifying a lower subscript level in a naked reference resets the naked indicator to that subscript level. Therefore, when using a bare global variable reference, the subscript level established by the latest global reference is always used.

The bare indicator value is recorded in the $ZREFERENCE special variable. The naked indicator is initialized as an empty string. Attempting a NAKED global reference without a NAKED indicator set results in a

error. Changing the namespace reinitializes the nudity indicator. You can reinitialize the bare indicator by setting $ZREFERENCE to an empty string (” “).

In the example below, GLOBAL ^Produce(” fruit “,1) is specified in the first reference with the sublabel. InterSystems IRIS saves this global variable name and subscript in the nudity indicator so that subsequent nudity global references can omit the global name “Production” and the higher subscript level “Fruit”. When ^(3,1) bare references reach a lower subscript level, this new subscript level becomes the assumption for any subsequent bare global variable references.

/// w ##class(PHA.TEST.Global).GlobalNake()
ClassMethod GlobalNake(a)
{
	SET ^Produce("fruit".1) ="Apples"  /* Full global variable references */
	SET ^(2) ="Oranges"                /* Naked global references */
	SET ^(3) ="Pears"                  /* Assume the subscript level is 2 */
	SET ^(3.1) ="Bartlett pears"       /* Go to subscript level 3 */
	SET ^(2) ="Anjou pears"            /* Assume the subscript level is 3 */
	WRITE "latest global reference is: ",$ZREFERENCE,!
	ZWRITE ^Produce
	KILL ^Produce
	q ""
}
Copy the code
DHC-APP>w ##class(PHA.TEST.Global).GlobalNake(a)latest global reference is: ^Produce("fruit^ ", 3, 2)Produce("fruit", 1)="Apples"
^Produce("fruit".2) ="Oranges"
^Produce("fruit".3) ="Pears"
^Produce("fruit".3.1) ="Bartlett pears"
^Produce("fruit".3.2) ="Anjou pears"
Copy the code

With rare exceptions, every global variable-reference (full or bare) sets a bare indicator. The $ZREFERENCE special variable contains the full global name and subscript of the latest global variable reference, even if it is a bare global reference. The ZWRITE command also displays the full global name and subscript for each global, whether or not it is set with a bare reference.

Bare global variable references should be used with caution, as InterSystems IRIS sets bare indicators in situations that are not always obvious, including the following:

  • A full global variable reference initially sets the naked indicator, and subsequent full or bare global references change the naked indicator, even if the global reference is unsuccessful. For example, an attempt to write a value to a global variable that does not exist sets a bare indicator.
  • Postcondition commands that reference subscripts globally set bare indicators regardless of how InterSystems IRIS calculates postconditions.
  • Optional function parameters that reference subscript global variables may or may not have a bare indicator set, depending on whether IRIS counts all parameters. For example,$getThe second argument to always sets a bare indicator, even if it contains a default value that is not used. InterSystems IRIS evaluates parameters from left to right, so the last parameter may reset the bare indicator set by the first parameter.
  • Rollback transactionTROLLBACKThe command does not roll back a bare indicator to the value at the start of the transaction.

If a full global variable reference contains an extended global variable reference, subsequent bare global references will use the same extended global reference; An extended reference does not have to be specified as part of a bare global reference.